diff --git a/application/pom.xml b/application/pom.xml index c4766f2ba8..d99f60773e 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -57,20 +57,20 @@ rule-engine-components - org.thingsboard.common - transport + org.thingsboard.common.transport + transport-api - org.thingsboard.transport - http + org.thingsboard.common.transport + mqtt - org.thingsboard.transport - coap + org.thingsboard.common.transport + http - org.thingsboard.transport - mqtt + org.thingsboard.common.transport + coap org.thingsboard @@ -542,7 +542,8 @@ -PprojectBuildDir=${project.build.directory} -PprojectVersion=${project.version} - -PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + -PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + -PpkgName=${pkg.name} -PpkgInstallFolder=${pkg.installFolder} -PpkgLogFolder=${pkg.unixLogFolder} diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 8930f8476e..5e19d690c4 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -31,6 +31,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.actors.service.ActorService; @@ -63,12 +64,12 @@ import org.thingsboard.server.service.encoding.DataDecodingEncodingService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.mail.MailExecutorService; -import org.thingsboard.server.service.queue.MsgQueueService; import org.thingsboard.server.service.rpc.DeviceRpcService; import org.thingsboard.server.service.script.JsExecutorService; import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; +import org.thingsboard.server.service.transport.RuleEngineTransportService; import javax.annotation.Nullable; import java.io.IOException; @@ -198,11 +199,12 @@ public class ActorSystemContext { @Autowired @Getter - private MsgQueueService msgQueueService; + private DeviceStateService deviceStateService; + @Lazy @Autowired @Getter - private DeviceStateService deviceStateService; + private RuleEngineTransportService ruleEngineTransportService; @Value("${cluster.partition_id}") @Getter @@ -260,10 +262,6 @@ public class ActorSystemContext { @Setter private ActorRef appActor; - @Getter - @Setter - private ActorRef sessionManagerActor; - @Getter @Setter private ActorRef statsActor; diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 6a78f78d1b..f4373db9be 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -38,8 +38,6 @@ import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.core.BasicActorSystemToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.dao.model.ModelConstants; @@ -105,7 +103,7 @@ public class AppActor extends RuleChainManagerActor { case SERVICE_TO_RULE_ENGINE_MSG: onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); break; - case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG: + case TRANSPORT_TO_DEVICE_ACTOR_MSG: case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG: case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG: case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG: @@ -114,19 +112,12 @@ public class AppActor extends RuleChainManagerActor { case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG: onToDeviceActorMsg((TenantAwareMsg) msg); break; - case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG: - onToDeviceSessionMsg((BasicActorSystemToDeviceSessionActorMsg) msg); - break; default: return false; } return true; } - private void onToDeviceSessionMsg(BasicActorSystemToDeviceSessionActorMsg msg) { - systemContext.getSessionManagerActor().tell(msg, self()); - } - private void onPossibleClusterMsg(SendToClusterMsg msg) { Optional address = systemContext.getRoutingService().resolveById(msg.getEntityId()); if (address.isPresent()) { @@ -169,16 +160,6 @@ public class AppActor extends RuleChainManagerActor { getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender()); } - private void processDeviceMsg(DeviceToDeviceActorMsg deviceToDeviceActorMsg) { - TenantId tenantId = deviceToDeviceActorMsg.getTenantId(); - ActorRef tenantActor = getOrCreateTenantActor(tenantId); - if (deviceToDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) { -// tenantActor.tell(new RuleChainDeviceMsg(deviceToDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self()); - } else { - tenantActor.tell(deviceToDeviceActorMsg, context().self()); - } - } - private ActorRef getOrCreateTenantActor(TenantId tenantId) { return tenantActors.computeIfAbsent(tenantId, k -> context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId)) .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString())); diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java index 99d004574e..bd2a0f4486 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java @@ -26,12 +26,11 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg; -import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; +import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; public class DeviceActor extends ContextAwareActor { @@ -50,8 +49,8 @@ public class DeviceActor extends ContextAwareActor { case CLUSTER_EVENT_MSG: processor.processClusterEventMsg((ClusterEventMsg) msg); break; - case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG: - processor.process(context(), (DeviceToDeviceActorMsg) msg); + case TRANSPORT_TO_DEVICE_ACTOR_MSG: + processor.process(context(), (TransportToDeviceActorMsgWrapper) msg); break; case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG: processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg); @@ -74,12 +73,6 @@ public class DeviceActor extends ContextAwareActor { case DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG: processor.processClientSideRpcTimeout(context(), (DeviceActorClientSideRpcTimeoutMsg) msg); break; - case DEVICE_ACTOR_QUEUE_TIMEOUT_MSG: - processor.processQueueTimeout(context(), (DeviceActorQueueTimeoutMsg) msg); - break; - case RULE_ENGINE_QUEUE_PUT_ACK_MSG: - processor.processQueueAck(context(), (RuleEngineQueuePutAckMsg) msg); - break; default: return false; } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index a2ea0487cf..05902a6146 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -16,7 +16,6 @@ package org.thingsboard.server.actors.device; import akka.actor.ActorContext; -import akka.actor.ActorRef; import akka.event.LoggingAdapter; import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.FutureCallback; @@ -33,7 +32,6 @@ import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.SessionId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -44,36 +42,34 @@ import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.core.AttributesUpdateNotification; -import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; -import org.thingsboard.server.common.msg.core.BasicActorSystemToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.core.BasicCommandAckResponse; -import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse; -import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; -import org.thingsboard.server.common.msg.core.GetAttributesRequest; -import org.thingsboard.server.common.msg.core.RuleEngineError; -import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg; -import org.thingsboard.server.common.msg.core.SessionCloseMsg; -import org.thingsboard.server.common.msg.core.SessionCloseNotification; -import org.thingsboard.server.common.msg.core.SessionOpenMsg; -import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; -import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg; -import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg; -import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg; -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; -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.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg; -import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; +import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; +import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; +import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import javax.annotation.Nullable; import java.util.ArrayList; @@ -87,9 +83,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; -import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -99,12 +93,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso private final TenantId tenantId; private final DeviceId deviceId; - private final Map sessions; - private final Map attributeSubscriptions; - private final Map rpcSubscriptions; + private final Map sessions; + private final Map attributeSubscriptions; + private final Map rpcSubscriptions; private final Map toDeviceRpcPendingMap; private final Map toServerRpcPendingMap; - private final Map pendingMsgs; private final Gson gson = new Gson(); private final JsonParser jsonParser = new JsonParser(); @@ -123,7 +116,6 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso this.rpcSubscriptions = new HashMap<>(); this.toDeviceRpcPendingMap = new HashMap<>(); this.toServerRpcPendingMap = new HashMap<>(); - this.pendingMsgs = new HashMap<>(); initAttributes(); } @@ -139,11 +131,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) { ToDeviceRpcRequest request = msg.getMsg(); ToDeviceRpcRequestBody body = request.getBody(); - ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( - rpcSeq++, - body.getMethod(), - body.getParams() - ); + ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId( + rpcSeq++).setMethodName(body.getMethod()).setParams(body.getParams()).build(); long timeout = request.getExpirationTime() - System.currentTimeMillis(); if (timeout <= 0) { @@ -152,11 +141,10 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } boolean sent = rpcSubscriptions.size() > 0; - Set syncSessionSet = new HashSet<>(); + Set syncSessionSet = new HashSet<>(); rpcSubscriptions.entrySet().forEach(sub -> { - ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sub.getKey()); - sendMsgToSessionActor(response, sub.getValue().getServer()); - if (SessionType.SYNC == sub.getValue().getType()) { + sendToTransport(rpcRequest, sub.getKey(), sub.getValue().getNodeId()); + if (TransportProtos.SessionType.SYNC == sub.getValue().getType()) { syncSessionSet.add(sub.getKey()); } }); @@ -191,32 +179,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } } - void processQueueTimeout(ActorContext context, DeviceActorQueueTimeoutMsg msg) { - PendingSessionMsgData data = pendingMsgs.remove(msg.getId()); - if (data != null) { - logger.debug("[{}] Queue put [{}] timeout detected!", deviceId, msg.getId()); - ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(data.getSessionMsgType(), RuleEngineError.QUEUE_PUT_TIMEOUT); - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress()); - } - } - - void processQueueAck(ActorContext context, RuleEngineQueuePutAckMsg msg) { - PendingSessionMsgData data = pendingMsgs.remove(msg.getId()); - if (data != null && data.isReplyOnQueueAck()) { - int remainingAcks = data.getAckMsgCount() - 1; - data.setAckMsgCount(remainingAcks); - logger.debug("[{}] Queue put [{}] ack detected. Remaining acks: {}!", deviceId, msg.getId(), remainingAcks); - if (remainingAcks == 0) { - ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onSuccess(data.getSessionMsgType(), data.getRequestId()); - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress()); - } - } - } - - private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional server) { + private void sendPendingRequests(ActorContext context, UUID sessionId, SessionInfoProto sessionInfo) { + TransportProtos.SessionType sessionType = getSessionType(sessionId); if (!toDeviceRpcPendingMap.isEmpty()) { logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); - if (type == SessionType.SYNC) { + if (sessionType == TransportProtos.SessionType.SYNC) { logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); rpcSubscriptions.remove(sessionId); } @@ -224,16 +191,16 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); } Set sentOneWayIds = new HashSet<>(); - if (type == SessionType.ASYNC) { - toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds)); + if (sessionType == TransportProtos.SessionType.ASYNC) { + toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds)); } else { - toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds)); + toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, sessionInfo.getNodeId(), sentOneWayIds)); } sentOneWayIds.forEach(toDeviceRpcPendingMap::remove); } - private Consumer> processPendingRpc(ActorContext context, SessionId sessionId, Optional server, Set sentOneWayIds) { + private Consumer> processPendingRpc(ActorContext context, UUID sessionId, String nodeId, Set sentOneWayIds) { return entry -> { ToDeviceRpcRequestActorMsg requestActorMsg = entry.getValue().getMsg(); ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg(); @@ -242,40 +209,40 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso sentOneWayIds.add(entry.getKey()); systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(request.getId(), requestActorMsg.getServerAddress(), null, null)); } - ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( - entry.getKey(), - body.getMethod(), - body.getParams() - ); - ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(rpcRequest, sessionId); - sendMsgToSessionActor(response, server); + ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId( + entry.getKey()).setMethodName(body.getMethod()).setParams(body.getParams()).build(); + sendToTransport(rpcRequest, sessionId, nodeId); }; } - void process(ActorContext context, DeviceToDeviceActorMsg msg) { - processSubscriptionCommands(context, msg); - processRpcResponses(context, msg); - processSessionStateMsgs(msg); - - SessionMsgType sessionMsgType = msg.getPayload().getMsgType(); - if (sessionMsgType.requiresRulesProcessing()) { - switch (sessionMsgType) { - case GET_ATTRIBUTES_REQUEST: - handleGetAttributesRequest(msg); - break; - case POST_ATTRIBUTES_REQUEST: - handlePostAttributesRequest(context, msg); - reportActivity(); - break; - case POST_TELEMETRY_REQUEST: - handlePostTelemetryRequest(context, msg); - reportActivity(); - break; - case TO_SERVER_RPC_REQUEST: - handleClientSideRPCRequest(context, msg); - reportActivity(); - break; - } + void process(ActorContext context, TransportToDeviceActorMsgWrapper wrapper) { + TransportToDeviceActorMsg msg = wrapper.getMsg(); + if (msg.hasSessionEvent()) { + processSessionStateMsgs(msg.getSessionInfo(), msg.getSessionEvent()); + } + if (msg.hasSubscribeToAttributes()) { + processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToAttributes()); + } + if (msg.hasSubscribeToRPC()) { + processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToRPC()); + } + if (msg.hasPostAttributes()) { + handlePostAttributesRequest(context, msg.getSessionInfo(), msg.getPostAttributes()); + reportActivity(); + } + if (msg.hasPostTelemetry()) { + handlePostTelemetryRequest(context, msg.getSessionInfo(), msg.getPostTelemetry()); + reportActivity(); + } + if (msg.hasGetAttributes()) { + handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes()); + } + if (msg.hasToDeviceRPCCallResponse()) { + processRpcResponses(context, msg.getSessionInfo(), msg.getToDeviceRPCCallResponse()); + } + if (msg.hasToServerRPCCallRequest()) { + handleClientSideRPCRequest(context, msg.getSessionInfo(), msg.getToServerRPCCallRequest()); + reportActivity(); } } @@ -291,27 +258,27 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso systemContext.getDeviceStateService().onDeviceDisconnect(deviceId); } - private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) { - GetAttributesRequest request = (GetAttributesRequest) src.getPayload(); - ListenableFuture> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, request.getClientAttributeNames()); - ListenableFuture> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, request.getSharedAttributeNames()); - + private void handleGetAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { + ListenableFuture> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, toOptionalSet(request.getClientAttributeNamesList())); + ListenableFuture> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, toOptionalSet(request.getSharedAttributeNamesList())); + int requestId = request.getRequestId(); Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback>>() { @Override public void onSuccess(@Nullable List> result) { - BasicGetAttributesResponse response = BasicGetAttributesResponse.onSuccess(request.getMsgType(), - request.getRequestId(), BasicAttributeKVMsg.from(result.get(0), result.get(1))); - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(response, src.getSessionId()), src.getServerAddress()); + GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() + .setRequestId(requestId) + .addAllClientAttributeList(toTsKvProtos(result.get(0))) + .addAllSharedAttributeList(toTsKvProtos(result.get(1))) + .build(); + sendToTransport(responseMsg, sessionInfo); } @Override public void onFailure(Throwable t) { - if (t instanceof Exception) { - ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onError(SessionMsgType.GET_ATTRIBUTES_REQUEST, request.getRequestId(), (Exception) t); - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, src.getSessionId()), src.getServerAddress()); - } else { - logger.error("[{}] Failed to process attributes request", deviceId, t); - } + GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder() + .setError(t.getMessage()) + .build(); + sendToTransport(responseMsg, sessionInfo); } }); } @@ -328,115 +295,98 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } } - private void handlePostAttributesRequest(ActorContext context, DeviceToDeviceActorMsg src) { - AttributesUpdateRequest request = (AttributesUpdateRequest) src.getPayload(); - - JsonObject json = new JsonObject(); - for (AttributeKvEntry kv : request.getAttributes()) { - kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - } - - TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); - PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), - SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1); - pushToRuleEngineWithTimeout(context, tbMsg, msgData); + private void handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) { + JsonObject json = getJsonObject(postAttributes.getKvList()); + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(), + TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); + pushToRuleEngine(context, tbMsg); } - private void handlePostTelemetryRequest(ActorContext context, DeviceToDeviceActorMsg src) { - TelemetryUploadRequest request = (TelemetryUploadRequest) src.getPayload(); - - Map> tsData = request.getData(); - - PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), - SessionMsgType.POST_TELEMETRY_REQUEST, request.getRequestId(), true, tsData.size()); - - for (Map.Entry> entry : tsData.entrySet()) { - JsonObject json = new JsonObject(); - for (KvEntry kv : entry.getValue()) { - kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); - } + private void handlePostTelemetryRequest(ActorContext context, SessionInfoProto sessionInfo, PostTelemetryMsg postTelemetry) { + for (TsKvListProto tsKv : postTelemetry.getTsKvListList()) { + JsonObject json = getJsonObject(tsKv.getKvList()); TbMsgMetaData metaData = defaultMetaData.copy(); - metaData.putValue("ts", entry.getKey() + ""); + metaData.putValue("ts", tsKv.getTs() + ""); TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); - pushToRuleEngineWithTimeout(context, tbMsg, msgData); + pushToRuleEngine(context, tbMsg); } } - private void handleClientSideRPCRequest(ActorContext context, DeviceToDeviceActorMsg src) { - ToServerRpcRequestMsg request = (ToServerRpcRequestMsg) src.getPayload(); - + private void handleClientSideRPCRequest(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg request) { + UUID sessionId = getSessionId(sessionInfo); JsonObject json = new JsonObject(); - json.addProperty("method", request.getMethod()); + json.addProperty("method", request.getMethodName()); json.add("params", jsonParser.parse(request.getParams())); TbMsgMetaData requestMetaData = defaultMetaData.copy(); requestMetaData.putValue("requestId", Integer.toString(request.getRequestId())); TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, requestMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); - PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), SessionMsgType.TO_SERVER_RPC_REQUEST, request.getRequestId(), false, 1); - pushToRuleEngineWithTimeout(context, tbMsg, msgData); + context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self()); scheduleMsgWithDelay(context, new DeviceActorClientSideRpcTimeoutMsg(request.getRequestId(), systemContext.getClientSideRpcTimeout()), systemContext.getClientSideRpcTimeout()); - toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(src.getSessionId(), src.getSessionType(), src.getServerAddress())); + toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(sessionId, getSessionType(sessionId), sessionInfo.getNodeId())); + } + + private TransportProtos.SessionType getSessionType(UUID sessionId) { + return sessions.containsKey(sessionId) ? TransportProtos.SessionType.ASYNC : TransportProtos.SessionType.SYNC; } - public void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) { + void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) { ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId()); if (data != null) { logger.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId()); - ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(SessionMsgType.TO_SERVER_RPC_REQUEST, RuleEngineError.TIMEOUT); - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServer()); + sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() + .setRequestId(msg.getId()).setError("timeout").build() + , data.getSessionId(), data.getNodeId()); } } void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) { - ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId()); + int requestId = msg.getMsg().getRequestId(); + ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(requestId); if (data != null) { - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(msg.getMsg(), data.getSessionId()), data.getServer()); + sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() + .setRequestId(requestId).setPayload(msg.getMsg().getData()).build() + , data.getSessionId(), data.getNodeId()); } } - private void pushToRuleEngineWithTimeout(ActorContext context, TbMsg tbMsg, PendingSessionMsgData pendingMsgData) { - SessionMsgType sessionMsgType = pendingMsgData.getSessionMsgType(); - int requestId = pendingMsgData.getRequestId(); - if (systemContext.isQueuePersistenceEnabled()) { - pendingMsgs.put(tbMsg.getId(), pendingMsgData); - scheduleMsgWithDelay(context, new DeviceActorQueueTimeoutMsg(tbMsg.getId(), systemContext.getQueuePersistenceTimeout()), systemContext.getQueuePersistenceTimeout()); - } else { - ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(BasicStatusCodeResponse.onSuccess(sessionMsgType, requestId), pendingMsgData.getSessionId()); - sendMsgToSessionActor(response, pendingMsgData.getServerAddress()); - } + private void pushToRuleEngine(ActorContext context, TbMsg tbMsg) { context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self()); } void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) { if (attributeSubscriptions.size() > 0) { - ToDeviceMsg notification = null; + boolean hasNotificationData = false; + AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder(); if (msg.isDeleted()) { - List sharedKeys = msg.getDeletedKeys().stream() + List sharedKeys = msg.getDeletedKeys().stream() .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) + .map(AttributeKey::getAttributeKey) .collect(Collectors.toList()); - notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys)); + if (!sharedKeys.isEmpty()) { + notification.addAllSharedDeleted(sharedKeys); + hasNotificationData = true; + } } else { if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) { List attributes = new ArrayList<>(msg.getValues()); if (attributes.size() > 0) { - notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes)); + List sharedUpdated = msg.getValues().stream().map(this::toTsKvProto) + .collect(Collectors.toList()); + if (!sharedUpdated.isEmpty()) { + notification.addAllSharedUpdated(sharedUpdated); + hasNotificationData = true; + } } else { logger.debug("[{}] No public server side attributes changed!", deviceId); } } } - if (notification != null) { - ToDeviceMsg finalNotification = notification; + if (hasNotificationData) { + AttributeUpdateNotificationMsg finalNotification = notification.build(); attributeSubscriptions.entrySet().forEach(sub -> { - ActorSystemToDeviceSessionActorMsg response = new BasicActorSystemToDeviceSessionActorMsg(finalNotification, sub.getKey()); - sendMsgToSessionActor(response, sub.getValue().getServer()); + sendToTransport(finalNotification, sub.getKey(), sub.getValue().getNodeId()); }); } } else { @@ -444,75 +394,83 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } } - private void processRpcResponses(ActorContext context, DeviceToDeviceActorMsg msg) { - SessionId sessionId = msg.getSessionId(); - FromDeviceMsg inMsg = msg.getPayload(); - if (inMsg.getMsgType() == SessionMsgType.TO_DEVICE_RPC_RESPONSE) { - logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); - ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg; - ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); - boolean success = requestMd != null; - if (success) { - systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), - requestMd.getMsg().getServerAddress(), responseMsg.getData(), null)); - } else { - logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); - } - if (msg.getSessionType() == SessionType.SYNC) { - BasicCommandAckResponse response = success - ? BasicCommandAckResponse.onSuccess(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId()) - : BasicCommandAckResponse.onError(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException()); - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress()); - } + private void processRpcResponses(ActorContext context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) { + UUID sessionId = getSessionId(sessionInfo); + logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); + boolean success = requestMd != null; + if (success) { + systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), + requestMd.getMsg().getServerAddress(), responseMsg.getPayload(), null)); + } else { + logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); } } void processClusterEventMsg(ClusterEventMsg msg) { - if (!msg.isAdded()) { - logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress()); - Predicate> filter = e -> e.getValue().getServer() - .map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false); - attributeSubscriptions.entrySet().removeIf(filter); - rpcSubscriptions.entrySet().removeIf(filter); - } +// if (!msg.isAdded()) { +// logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress()); +// Predicate> filter = e -> e.getValue().getServer() +// .map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false); +// attributeSubscriptions.entrySet().removeIf(filter); +// rpcSubscriptions.entrySet().removeIf(filter); +// } } - private void processSubscriptionCommands(ActorContext context, DeviceToDeviceActorMsg msg) { - SessionId sessionId = msg.getSessionId(); - SessionType sessionType = msg.getSessionType(); - FromDeviceMsg inMsg = msg.getPayload(); - if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) { - logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); - attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); - } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) { + private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) { + UUID sessionId = getSessionId(sessionInfo); + if (subscribeCmd.getUnsubscribe()) { logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); attributeSubscriptions.remove(sessionId); - } else if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) { - logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); - rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); - sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress()); - } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) { - logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); + } else { + SessionInfo session = sessions.get(sessionId); + if (session == null) { + session = new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId()); + } + logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); + attributeSubscriptions.put(sessionId, session); + } + } + + private UUID getSessionId(SessionInfoProto sessionInfo) { + return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); + } + + private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) { + UUID sessionId = getSessionId(sessionInfo); + if (subscribeCmd.getUnsubscribe()) { + logger.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId); rpcSubscriptions.remove(sessionId); + } else { + SessionInfo session = sessions.get(sessionId); + if (session == null) { + session = new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId()); + } + logger.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); + rpcSubscriptions.put(sessionId, session); + sendPendingRequests(context, sessionId, sessionInfo); } } - private void processSessionStateMsgs(DeviceToDeviceActorMsg msg) { - SessionId sessionId = msg.getSessionId(); - FromDeviceMsg inMsg = msg.getPayload(); - if (inMsg instanceof SessionOpenMsg) { + private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) { + UUID sessionId = getSessionId(sessionInfo); + if (msg.getEvent() == SessionEvent.OPEN) { + if(sessions.containsKey(sessionId)){ + logger.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); + return; + } logger.debug("[{}] Processing new session [{}]", deviceId, sessionId); if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { - SessionId sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null); + UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null); if (sessionIdToRemove != null) { closeSession(sessionIdToRemove, sessions.remove(sessionIdToRemove)); } } - sessions.put(sessionId, new SessionInfo(SessionType.ASYNC, msg.getServerAddress())); + sessions.put(sessionId, new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfo.getNodeId())); if (sessions.size() == 1) { reportSessionOpen(); } - } else if (inMsg instanceof SessionCloseMsg) { + } else if (msg.getEvent() == SessionEvent.CLOSED) { logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); sessions.remove(sessionId); attributeSubscriptions.remove(sessionId); @@ -523,25 +481,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } } - private void sendMsgToSessionActor(ActorSystemToDeviceSessionActorMsg response, Optional sessionAddress) { - if (sessionAddress.isPresent()) { - ServerAddress address = sessionAddress.get(); - logger.debug("{} Forwarding msg: {}", address, response); - systemContext.getRpcService().tell(systemContext.getEncodingService() - .convertToProtoDataMessage(sessionAddress.get(), response)); - } else { - systemContext.getSessionManagerActor().tell(response, ActorRef.noSender()); - } - } - void processCredentialsUpdate() { sessions.forEach(this::closeSession); attributeSubscriptions.clear(); rpcSubscriptions.clear(); } - private void closeSession(SessionId sessionId, SessionInfo sessionInfo) { - sendMsgToSessionActor(new BasicActorSystemToDeviceSessionActorMsg(new SessionCloseNotification(), sessionId), sessionInfo.getServer()); + private void closeSession(UUID sessionId, SessionInfo sessionInfo) { + DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder() + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setSessionCloseNotification(SessionCloseNotificationProto.getDefaultInstance()).build(); + systemContext.getRuleEngineTransportService().process(sessionInfo.getNodeId(), msg); } void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) { @@ -552,4 +503,107 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso this.defaultMetaData.putValue("deviceType", deviceType); } + private JsonObject getJsonObject(List tsKv) { + JsonObject json = new JsonObject(); + for (KeyValueProto kv : tsKv) { + switch (kv.getType()) { + case BOOLEAN_V: + json.addProperty(kv.getKey(), kv.getBoolV()); + break; + case LONG_V: + json.addProperty(kv.getKey(), kv.getLongV()); + break; + case DOUBLE_V: + json.addProperty(kv.getKey(), kv.getDoubleV()); + break; + case STRING_V: + json.addProperty(kv.getKey(), kv.getStringV()); + break; + } + } + return json; + } + + private Optional> toOptionalSet(List strings) { + if (strings == null || strings.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(new HashSet<>(strings)); + } + } + + private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) { + DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder() + .setSessionIdMSB(sessionInfo.getSessionIdMSB()) + .setSessionIdLSB(sessionInfo.getSessionIdLSB()) + .setGetAttributesResponse(responseMsg).build(); + systemContext.getRuleEngineTransportService().process(sessionInfo.getNodeId(), msg); + } + + private void sendToTransport(AttributeUpdateNotificationMsg notificationMsg, UUID sessionId, String nodeId) { + DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder() + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setAttributeUpdateNotification(notificationMsg).build(); + systemContext.getRuleEngineTransportService().process(nodeId, msg); + } + + private void sendToTransport(ToDeviceRpcRequestMsg rpcMsg, UUID sessionId, String nodeId) { + DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder() + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setToDeviceRequest(rpcMsg).build(); + systemContext.getRuleEngineTransportService().process(nodeId, msg); + } + + private void sendToTransport(TransportProtos.ToServerRpcResponseMsg rpcMsg, UUID sessionId, String nodeId) { + DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder() + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setToServerResponse(rpcMsg).build(); + systemContext.getRuleEngineTransportService().process(nodeId, msg); + } + + + private List toTsKvProtos(@Nullable List result) { + List clientAttributes; + if (result == null || result.isEmpty()) { + clientAttributes = Collections.emptyList(); + } else { + clientAttributes = new ArrayList<>(result.size()); + for (AttributeKvEntry attrEntry : result) { + clientAttributes.add(toTsKvProto(attrEntry)); + } + } + return clientAttributes; + } + + private TsKvProto toTsKvProto(AttributeKvEntry attrEntry) { + return TsKvProto.newBuilder().setTs(attrEntry.getLastUpdateTs()) + .setKv(toKeyValueProto(attrEntry)).build(); + } + + private KeyValueProto toKeyValueProto(KvEntry kvEntry) { + KeyValueProto.Builder builder = KeyValueProto.newBuilder(); + builder.setKey(kvEntry.getKey()); + switch (kvEntry.getDataType()) { + case BOOLEAN: + builder.setType(KeyValueType.BOOLEAN_V); + builder.setBoolV(kvEntry.getBooleanValue().get()); + break; + case DOUBLE: + builder.setType(KeyValueType.DOUBLE_V); + builder.setDoubleV(kvEntry.getDoubleValue().get()); + break; + case LONG: + builder.setType(KeyValueType.LONG_V); + builder.setLongV(kvEntry.getLongValue().get()); + break; + case STRING: + builder.setType(KeyValueType.STRING_V); + builder.setStringV(kvEntry.getStrValue().get()); + break; + } + return builder.build(); + } } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java b/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java deleted file mode 100644 index 23ad966049..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java +++ /dev/null @@ -1,40 +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.actors.device; - -import lombok.AllArgsConstructor; -import lombok.Data; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -import java.util.Optional; - -/** - * Created by ashvayka on 17.04.18. - */ -@Data -@AllArgsConstructor -public final class PendingSessionMsgData { - - private final SessionId sessionId; - private final Optional serverAddress; - private final SessionMsgType sessionMsgType; - private final int requestId; - private final boolean replyOnQueueAck; - private int ackMsgCount; - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java index 04c457c92e..43ae592cee 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java @@ -16,10 +16,7 @@ package org.thingsboard.server.actors.device; import lombok.Data; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.session.SessionType; - -import java.util.Optional; +import org.thingsboard.server.gen.transport.TransportProtos.SessionType; /** * @author Andrew Shvayka @@ -27,5 +24,5 @@ import java.util.Optional; @Data public class SessionInfo { private final SessionType type; - private final Optional server; + private final String nodeId; } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java index f82a8c2481..669d94b17d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java @@ -16,18 +16,16 @@ package org.thingsboard.server.actors.device; import lombok.Data; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.session.SessionType; +import org.thingsboard.server.gen.transport.TransportProtos; -import java.util.Optional; +import java.util.UUID; /** * @author Andrew Shvayka */ @Data public class ToServerRpcRequestMetadata { - private final SessionId sessionId; - private final SessionType type; - private final Optional server; + private final UUID sessionId; + private final TransportProtos.SessionType type; + private final String nodeId; } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index fe02335223..3da90d1e74 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -25,7 +25,6 @@ import java.util.Optional; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; -import org.thingsboard.server.actors.device.RuleEngineQueuePutAckMsg; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; import org.thingsboard.server.common.data.EntityType; @@ -90,26 +89,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList) { - for (RuleNode ruleNode : ruleNodeList) { - for (TbMsg tbMsg : queue.findUnprocessed(tenantId, ruleNode.getId().getId(), systemContext.getQueuePartitionId())) { - pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, ""); - } - } - if (firstNode != null) { - for (TbMsg tbMsg : queue.findUnprocessed(tenantId, entityId.getId(), systemContext.getQueuePartitionId())) { - pushMsgToNode(firstNode, tbMsg, ""); - } - } - } - @Override public void onUpdate(ActorContext context) throws Exception { RuleChain ruleChain = service.findRuleChainById(entityId); @@ -134,7 +119,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor pushMsgToNode(firstNode, msg, "")); + pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), ""); } } void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) { checkActive(); if (firstNode != null) { - putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> { - pushMsgToNode(firstNode, msg, ""); - envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self); - }); + pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getTbMsg()), ""); } } @@ -206,15 +187,16 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor pushMsgToNode(firstNode, msg, envelope.getFromRelationType())); + pushMsgToNode(firstNode, enrichWithRuleChainId(envelope.getMsg()), envelope.getFromRelationType()); } } else { if (firstNode != null) { pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType()); } else { - TbMsg msg = envelope.getMsg(); - EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId(); - queue.ack(tenantId, envelope.getMsg(), ackId.getId(), msg.getClusterPartition()); +// TODO: Ack this message in Kafka +// TbMsg msg = envelope.getMsg(); +// EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId(); +// queue.ack(tenantId, envelope.getMsg(), ackId.getId(), msg.getClusterPartition()); } } } @@ -249,7 +231,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor pushMsgToNode(targetNodeCtx, queuedMsg, fromRelationType)); + pushMsgToNode(targetNodeCtx, copy, fromRelationType); } private void pushToTarget(TbMsg msg, EntityId target, String fromRelationType) { diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index 1e9e23bae3..c2e8fd048a 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -30,7 +30,6 @@ import org.thingsboard.server.actors.app.AppActor; import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; import org.thingsboard.server.actors.rpc.RpcManagerActor; import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; -import org.thingsboard.server.actors.session.SessionManagerActor; import org.thingsboard.server.actors.stats.StatsActor; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; @@ -38,7 +37,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; @@ -90,8 +88,6 @@ public class DefaultActorService implements ActorService { private ActorRef appActor; - private ActorRef sessionManagerActor; - private ActorRef rpcManagerActor; @PostConstruct @@ -104,10 +100,6 @@ public class DefaultActorService implements ActorService { appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor"); actorContext.setAppActor(appActor); - sessionManagerActor = system.actorOf(Props.create(new SessionManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), - "sessionManagerActor"); - actorContext.setSessionManagerActor(sessionManagerActor); - rpcManagerActor = system.actorOf(Props.create(new RpcManagerActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), "rpcManagerActor"); @@ -134,12 +126,6 @@ public class DefaultActorService implements ActorService { appActor.tell(msg, ActorRef.noSender()); } - @Override - public void process(SessionAwareMsg msg) { - log.debug("Processing session aware msg: {}", msg); - sessionManagerActor.tell(msg, ActorRef.noSender()); - } - @Override public void onServerAdded(ServerInstance server) { log.trace("Processing onServerAdded msg: {}", server); @@ -194,7 +180,6 @@ public class DefaultActorService implements ActorService { private void broadcast(ClusterEventMsg msg) { this.appActor.tell(msg, ActorRef.noSender()); - this.sessionManagerActor.tell(msg, ActorRef.noSender()); this.rpcManagerActor.tell(msg, ActorRef.noSender()); } diff --git a/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java deleted file mode 100644 index a8f14fedf5..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/session/ASyncMsgProcessor.java +++ /dev/null @@ -1,156 +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.actors.session; - -import akka.actor.ActorContext; -import akka.event.LoggingAdapter; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.shared.SessionTimeoutMsg; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.core.AttributesSubscribeMsg; -import org.thingsboard.server.common.msg.core.ResponseMsg; -import org.thingsboard.server.common.msg.core.RpcSubscribeMsg; -import org.thingsboard.server.common.msg.core.SessionCloseMsg; -import org.thingsboard.server.common.msg.core.SessionOpenMsg; -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; -import org.thingsboard.server.common.msg.session.BasicSessionActorToAdaptorMsg; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.ex.SessionException; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor { - - private boolean firstMsg = true; - private Map pendingMap = new HashMap<>(); - private Optional currentTargetServer; - private boolean subscribedToAttributeUpdates; - private boolean subscribedToRpcCommands; - - public ASyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { - super(ctx, logger, sessionId); - } - - @Override - protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) { - updateSessionCtx(msg, SessionType.ASYNC); - DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg); - FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload(); - if (firstMsg) { - if (fromDeviceMsg.getMsgType() != SessionMsgType.SESSION_OPEN) { - toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m)); - } - firstMsg = false; - } - switch (fromDeviceMsg.getMsgType()) { - case POST_TELEMETRY_REQUEST: - case POST_ATTRIBUTES_REQUEST: - FromDeviceRequestMsg requestMsg = (FromDeviceRequestMsg) fromDeviceMsg; - if (requestMsg.getRequestId() >= 0) { - logger.debug("[{}] Pending request {} registered", requestMsg.getRequestId(), requestMsg.getMsgType()); - //TODO: handle duplicates. - pendingMap.put(requestMsg.getRequestId(), pendingMsg); - } - break; - case SUBSCRIBE_ATTRIBUTES_REQUEST: - subscribedToAttributeUpdates = true; - break; - case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - subscribedToAttributeUpdates = false; - break; - case SUBSCRIBE_RPC_COMMANDS_REQUEST: - subscribedToRpcCommands = true; - break; - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - subscribedToRpcCommands = false; - break; - default: - break; - } - currentTargetServer = forwardToAppActor(ctx, pendingMsg); - } - - @Override - public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { - try { - if (msg.getSessionMsgType() != SessionMsgType.SESSION_CLOSE) { - switch (msg.getSessionMsgType()) { - case STATUS_CODE_RESPONSE: - case GET_ATTRIBUTES_RESPONSE: - ResponseMsg responseMsg = (ResponseMsg) msg; - if (responseMsg.getRequestId() >= 0) { - logger.debug("[{}] Pending request processed: {}", responseMsg.getRequestId(), responseMsg); - pendingMap.remove(responseMsg.getRequestId()); - } - break; - default: - break; - } - sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); - } else { - sessionCtx.onMsg(org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg.onCredentialsRevoked(sessionCtx.getSessionId())); - } - } catch (SessionException e) { - logger.warning("Failed to push session response msg", e); - } - } - - @Override - public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { - // TODO Auto-generated method stub - } - - @Override - protected void cleanupSession(ActorContext ctx) { - toDeviceMsg(new SessionCloseMsg()).ifPresent(m -> forwardToAppActor(ctx, m)); - } - - @Override - public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { - if (pendingMap.size() > 0 || subscribedToAttributeUpdates || subscribedToRpcCommands) { - Optional newTargetServer = systemContext.getRoutingService().resolveById(getDeviceId()); - if (!newTargetServer.equals(currentTargetServer)) { - firstMsg = true; - currentTargetServer = newTargetServer; - pendingMap.values().forEach(v -> { - forwardToAppActor(context, v, currentTargetServer); - if (currentTargetServer.isPresent()) { - logger.debug("[{}] Forwarded msg to new server: {}", sessionId, currentTargetServer.get()); - } else { - logger.debug("[{}] Forwarded msg to local server.", sessionId); - } - }); - if (subscribedToAttributeUpdates) { - toDeviceMsg(new AttributesSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); - logger.debug("[{}] Forwarded attributes subscription.", sessionId); - } - if (subscribedToRpcCommands) { - toDeviceMsg(new RpcSubscribeMsg()).ifPresent(m -> forwardToAppActor(context, m, currentTargetServer)); - logger.debug("[{}] Forwarded rpc commands subscription.", sessionId); - } - } - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java deleted file mode 100644 index 469cda9c5f..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java +++ /dev/null @@ -1,122 +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.actors.session; - -import akka.actor.ActorContext; -import akka.actor.ActorRef; -import akka.event.LoggingAdapter; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; -import org.thingsboard.server.actors.shared.SessionTimeoutMsg; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.device.BasicDeviceToDeviceActorMsg; -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; -import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionContext; -import org.thingsboard.server.common.msg.session.SessionCtrlMsg; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; - -import java.util.Optional; - -abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgProcessor { - - protected final SessionId sessionId; - protected SessionContext sessionCtx; - protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype; - - protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { - super(ctx, logger); - this.sessionId = sessionId; - } - - protected abstract void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg); - - protected abstract void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg); - - protected abstract void processToDeviceMsg(ActorContext context, ToDeviceMsg msg); - - public abstract void processClusterEvent(ActorContext context, ClusterEventMsg msg); - - protected void processSessionCtrlMsg(ActorContext ctx, SessionCtrlMsg msg) { - if (msg instanceof SessionCloseMsg) { - cleanupSession(ctx); - terminateSession(ctx, sessionId); - } - } - - protected void cleanupSession(ActorContext ctx) { - } - - protected void updateSessionCtx(TransportToDeviceSessionActorMsg msg, SessionType type) { - sessionCtx = msg.getSessionMsg().getSessionContext(); - deviceToDeviceActorMsgPrototype = new BasicDeviceToDeviceActorMsg(msg, type); - } - - protected DeviceToDeviceActorMsg toDeviceMsg(TransportToDeviceSessionActorMsg msg) { - AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg(); - return new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, adaptorMsg.getMsg()); - } - - protected Optional toDeviceMsg(FromDeviceMsg msg) { - if (deviceToDeviceActorMsgPrototype != null) { - return Optional.of(new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, msg)); - } else { - return Optional.empty(); - } - } - - protected Optional forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward) { - Optional address = systemContext.getRoutingService().resolveById(toForward.getDeviceId()); - forwardToAppActor(ctx, toForward, address); - return address; - } - - protected Optional forwardToAppActorIfAddressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional oldAddress) { - - Optional newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId()); - if (!newAddress.equals(oldAddress)) { - getAppActor().tell(new SendToClusterMsg(toForward.getDeviceId(), toForward - .toOtherAddress(systemContext.getRoutingService().getCurrentServer())), ctx.self()); - } - return newAddress; - } - - protected void forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional address) { - if (address.isPresent()) { - systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(address.get(), - toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()))); - } else { - getAppActor().tell(toForward, ctx.self()); - } - } - - public static void terminateSession(ActorContext ctx, SessionId sessionId) { - ctx.parent().tell(new SessionTerminationMsg(sessionId), ActorRef.noSender()); - ctx.stop(ctx.self()); - } - - public DeviceId getDeviceId() { - return deviceToDeviceActorMsgPrototype.getDeviceId(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java deleted file mode 100644 index 05926c1825..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java +++ /dev/null @@ -1,143 +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.actors.session; - -import akka.actor.OneForOneStrategy; -import akka.actor.SupervisorStrategy; -import akka.event.Logging; -import akka.event.LoggingAdapter; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.service.ContextAwareActor; -import org.thingsboard.server.actors.service.ContextBasedCreator; -import org.thingsboard.server.actors.shared.SessionTimeoutMsg; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.SessionCtrlMsg; -import org.thingsboard.server.common.msg.session.SessionMsg; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; -import scala.concurrent.duration.Duration; - -public class SessionActor extends ContextAwareActor { - - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); - - private final SessionId sessionId; - private AbstractSessionActorMsgProcessor processor; - - private SessionActor(ActorSystemContext systemContext, SessionId sessionId) { - super(systemContext); - this.sessionId = sessionId; - } - - @Override - public SupervisorStrategy supervisorStrategy() { - return new OneForOneStrategy(-1, Duration.Inf(), - throwable -> { - logger.error(throwable, "Unknown session error"); - if (throwable instanceof Error) { - return OneForOneStrategy.escalate(); - } else { - return OneForOneStrategy.resume(); - } - }); - } - - @Override - protected boolean process(TbActorMsg msg) { - switch (msg.getMsgType()) { - case TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG: - processTransportToSessionMsg((TransportToDeviceSessionActorMsg) msg); - break; - case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG: - processActorsToSessionMsg((ActorSystemToDeviceSessionActorMsg) msg); - break; - case SESSION_TIMEOUT_MSG: - processTimeoutMsg((SessionTimeoutMsg) msg); - break; - case SESSION_CTRL_MSG: - processSessionCloseMsg((SessionCtrlMsg) msg); - break; - case CLUSTER_EVENT_MSG: - processClusterEvent((ClusterEventMsg) msg); - break; - default: return false; - } - return true; - } - - private void processClusterEvent(ClusterEventMsg msg) { - processor.processClusterEvent(context(), msg); - } - - private void processTransportToSessionMsg(TransportToDeviceSessionActorMsg msg) { - initProcessor(msg); - processor.processToDeviceActorMsg(context(), msg); - } - - private void processActorsToSessionMsg(ActorSystemToDeviceSessionActorMsg msg) { - processor.processToDeviceMsg(context(), msg.getMsg()); - } - - private void processTimeoutMsg(SessionTimeoutMsg msg) { - if (processor != null) { - processor.processTimeoutMsg(context(), msg); - } else { - logger.warning("[{}] Can't process timeout msg: {} without processor", sessionId, msg); - } - } - - private void processSessionCloseMsg(SessionCtrlMsg msg) { - if (processor != null) { - processor.processSessionCtrlMsg(context(), msg); - } else if (msg instanceof SessionCloseMsg) { - AbstractSessionActorMsgProcessor.terminateSession(context(), sessionId); - } else { - logger.warning("[{}] Can't process session ctrl msg: {} without processor", sessionId, msg); - } - } - - private void initProcessor(TransportToDeviceSessionActorMsg msg) { - if (processor == null) { - SessionMsg sessionMsg = (SessionMsg) msg.getSessionMsg(); - if (sessionMsg.getSessionContext().getSessionType() == SessionType.SYNC) { - processor = new SyncMsgProcessor(systemContext, logger, sessionId); - } else { - processor = new ASyncMsgProcessor(systemContext, logger, sessionId); - } - } - } - - public static class ActorCreator extends ContextBasedCreator { - private static final long serialVersionUID = 1L; - - private final SessionId sessionId; - - public ActorCreator(ActorSystemContext context, SessionId sessionId) { - super(context); - this.sessionId = sessionId; - } - - @Override - public SessionActor create() throws Exception { - return new SessionActor(context, sessionId); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java deleted file mode 100644 index 9039d302e3..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java +++ /dev/null @@ -1,180 +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.actors.session; - -import akka.actor.ActorInitializationException; -import akka.actor.ActorRef; -import akka.actor.InvalidActorNameException; -import akka.actor.LocalActorRef; -import akka.actor.OneForOneStrategy; -import akka.actor.Props; -import akka.actor.SupervisorStrategy; -import akka.actor.Terminated; -import akka.event.Logging; -import akka.event.LoggingAdapter; -import akka.japi.Function; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.service.ContextAwareActor; -import org.thingsboard.server.actors.service.ContextBasedCreator; -import org.thingsboard.server.actors.service.DefaultActorService; -import org.thingsboard.server.actors.shared.SessionTimeoutMsg; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.core.SessionCloseMsg; -import org.thingsboard.server.common.msg.session.SessionCtrlMsg; -import scala.concurrent.duration.Duration; - -import java.util.HashMap; -import java.util.Map; - -public class SessionManagerActor extends ContextAwareActor { - - private static final int INITIAL_SESSION_MAP_SIZE = 1024; - - private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); - - private final Map sessionActors; - - SessionManagerActor(ActorSystemContext systemContext) { - super(systemContext); - this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE); - } - - @Override - public SupervisorStrategy supervisorStrategy() { - return strategy; - } - - @Override - protected boolean process(TbActorMsg msg) { - //TODO Move everything here, to work with TbActorMsg - return false; - } - - @Override - public void onReceive(Object msg) throws Exception { - if (msg instanceof SessionCtrlMsg) { - onSessionCtrlMsg((SessionCtrlMsg) msg); - } else if (msg instanceof SessionAwareMsg) { - forwardToSessionActor((SessionAwareMsg) msg); - } else if (msg instanceof SessionTerminationMsg) { - onSessionTermination((SessionTerminationMsg) msg); - } else if (msg instanceof Terminated) { - onTermination((Terminated) msg); - } else if (msg instanceof SessionTimeoutMsg) { - onSessionTimeout((SessionTimeoutMsg) msg); - } else if (msg instanceof ClusterEventMsg) { - broadcast(msg); - } - } - - private void broadcast(Object msg) { - sessionActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); - } - - private void onSessionTimeout(SessionTimeoutMsg msg) { - String sessionIdStr = msg.getSessionId().toUidStr(); - ActorRef sessionActor = sessionActors.get(sessionIdStr); - if (sessionActor != null) { - sessionActor.tell(msg, ActorRef.noSender()); - } - } - - private void onSessionCtrlMsg(SessionCtrlMsg msg) { - String sessionIdStr = msg.getSessionId().toUidStr(); - ActorRef sessionActor = sessionActors.get(sessionIdStr); - if (sessionActor != null) { - sessionActor.tell(msg, ActorRef.noSender()); - } - } - - private void onSessionTermination(SessionTerminationMsg msg) { - String sessionIdStr = msg.getId().toUidStr(); - ActorRef sessionActor = sessionActors.remove(sessionIdStr); - if (sessionActor != null) { - log.debug("[{}] Removed session actor.", sessionIdStr); - //TODO: onSubscriptionUpdate device actor about session close; - } else { - log.debug("[{}] Session actor was already removed.", sessionIdStr); - } - } - - private void forwardToSessionActor(SessionAwareMsg msg) { - if (msg instanceof ActorSystemToDeviceSessionActorMsg || msg instanceof SessionCloseMsg) { - String sessionIdStr = msg.getSessionId().toUidStr(); - ActorRef sessionActor = sessionActors.get(sessionIdStr); - if (sessionActor != null) { - sessionActor.tell(msg, ActorRef.noSender()); - } else { - log.debug("[{}] Session actor was already removed.", sessionIdStr); - } - } else { - try { - getOrCreateSessionActor(msg.getSessionId()).tell(msg, self()); - } catch (InvalidActorNameException e) { - log.info("Invalid msg : {}", msg); - } - } - } - - private ActorRef getOrCreateSessionActor(SessionId sessionId) { - String sessionIdStr = sessionId.toUidStr(); - ActorRef sessionActor = sessionActors.get(sessionIdStr); - if (sessionActor == null) { - log.debug("[{}] Creating session actor.", sessionIdStr); - sessionActor = context().actorOf( - Props.create(new SessionActor.ActorCreator(systemContext, sessionId)).withDispatcher(DefaultActorService.SESSION_DISPATCHER_NAME), - sessionIdStr); - sessionActors.put(sessionIdStr, sessionActor); - log.debug("[{}] Created session actor.", sessionIdStr); - } - return sessionActor; - } - - private void onTermination(Terminated message) { - ActorRef terminated = message.actor(); - if (terminated instanceof LocalActorRef) { - log.info("Removed actor: {}.", terminated); - //TODO: cleanup session actors map - } else { - throw new IllegalStateException("Remote actors are not supported!"); - } - } - - public static class ActorCreator extends ContextBasedCreator { - private static final long serialVersionUID = 1L; - - public ActorCreator(ActorSystemContext context) { - super(context); - } - - @Override - public SessionManagerActor create() throws Exception { - return new SessionManagerActor(context); - } - } - - private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function() { - @Override - public SupervisorStrategy.Directive apply(Throwable t) { - logger.error(t, "Unknown failure"); - return SupervisorStrategy.stop(); - } - }); -} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java b/application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java deleted file mode 100644 index 38cf878fa7..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/session/SessionTerminationMsg.java +++ /dev/null @@ -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.actors.session; - -import org.thingsboard.server.actors.shared.ActorTerminationMsg; -import org.thingsboard.server.common.data.id.SessionId; - -public class SessionTerminationMsg extends ActorTerminationMsg { - - public SessionTerminationMsg(SessionId id) { - super(id); - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java deleted file mode 100644 index cf8df1385f..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java +++ /dev/null @@ -1,95 +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.actors.session; - -import akka.actor.ActorContext; -import akka.event.LoggingAdapter; -import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.shared.SessionTimeoutMsg; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; -import org.thingsboard.server.common.msg.session.BasicSessionActorToAdaptorMsg; -import org.thingsboard.server.common.msg.session.SessionContext; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; -import org.thingsboard.server.common.msg.session.ex.SessionException; - -import java.util.Optional; - -class SyncMsgProcessor extends AbstractSessionActorMsgProcessor { - private DeviceToDeviceActorMsg pendingMsg; - private Optional currentTargetServer; - private boolean pendingResponse; - - public SyncMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { - super(ctx, logger, sessionId); - } - - @Override - protected void processToDeviceActorMsg(ActorContext ctx, TransportToDeviceSessionActorMsg msg) { - updateSessionCtx(msg, SessionType.SYNC); - pendingMsg = toDeviceMsg(msg); - pendingResponse = true; - currentTargetServer = forwardToAppActor(ctx, pendingMsg); - scheduleMsgWithDelay(ctx, new SessionTimeoutMsg(sessionId), getTimeout(systemContext, msg.getSessionMsg().getSessionContext()), ctx.parent()); - } - - public void processTimeoutMsg(ActorContext context, SessionTimeoutMsg msg) { - if (pendingResponse) { - try { - sessionCtx.onMsg(SessionCloseMsg.onTimeout(sessionId)); - } catch (SessionException e) { - logger.warning("Failed to push session close msg", e); - } - terminateSession(context, this.sessionId); - } - } - - public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { - try { - sessionCtx.onMsg(new BasicSessionActorToAdaptorMsg(this.sessionCtx, msg)); - pendingResponse = false; - } catch (SessionException e) { - logger.warning("Failed to push session response msg", e); - } - terminateSession(context, this.sessionId); - } - - @Override - public void processClusterEvent(ActorContext context, ClusterEventMsg msg) { - if (pendingResponse) { - Optional newTargetServer = forwardToAppActorIfAddressChanged(context, pendingMsg, currentTargetServer); - if (logger.isDebugEnabled()) { - if (!newTargetServer.equals(currentTargetServer)) { - if (newTargetServer.isPresent()) { - logger.debug("[{}] Forwarded msg to new server: {}", sessionId, newTargetServer.get()); - } else { - logger.debug("[{}] Forwarded msg to local server.", sessionId); - } - } - } - currentTargetServer = newTargetServer; - } - } - - private long getTimeout(ActorSystemContext ctx, SessionContext sessionCtx) { - return sessionCtx.getTimeout() > 0 ? sessionCtx.getTimeout() : ctx.getSyncSessionTimeout(); - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java index 1cf83397fc..c9dc307451 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java @@ -26,7 +26,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.service.queue.MsgQueueService; import javax.annotation.Nullable; import java.util.function.Consumer; @@ -35,14 +34,12 @@ public abstract class ComponentMsgProcessor extends Abstract protected final TenantId tenantId; protected final T entityId; - protected final MsgQueueService queue; protected ComponentLifecycleState state; protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { super(systemContext, logger); this.tenantId = tenantId; this.entityId = id; - this.queue = systemContext.getMsgQueueService(); } public abstract void start(ActorContext context) throws Exception; @@ -82,22 +79,9 @@ public abstract class ComponentMsgProcessor extends Abstract protected void checkActive() { if (state != ComponentLifecycleState.ACTIVE) { - throw new IllegalStateException("Rule chain is not active!"); + logger.warning("Rule chain is not active. Current state [{}] for processor [{}] tenant [{}]", state, tenantId, entityId); + throw new IllegalStateException("Rule chain is not active! " + entityId + " - " + tenantId); } } - protected void putToQueue(final TbMsg tbMsg, final Consumer onSuccess) { - EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId(); - Futures.addCallback(queue.put(this.tenantId, tbMsg, entityId.getId(), tbMsg.getClusterPartition()), new FutureCallback() { - @Override - public void onSuccess(@Nullable Void result) { - onSuccess.accept(tbMsg); - } - - @Override - public void onFailure(Throwable t) { - logger.debug("Failed to push message [{}] to queue due to [{}]", tbMsg, t); - } - }); - } } diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java b/application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java deleted file mode 100644 index d015fe01de..0000000000 --- a/application/src/main/java/org/thingsboard/server/actors/shared/SessionTimeoutMsg.java +++ /dev/null @@ -1,36 +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.actors.shared; - -import lombok.Data; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.TbActorMsg; - -import java.io.Serializable; - -@Data -public class SessionTimeoutMsg implements Serializable, TbActorMsg { - - private static final long serialVersionUID = 1L; - - private final SessionId sessionId; - - @Override - public MsgType getMsgType() { - return MsgType.SESSION_TIMEOUT_MSG; - } -} diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 460b64c772..347483a185 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -87,7 +87,7 @@ public class TenantActor extends RuleChainManagerActor { case DEVICE_ACTOR_TO_RULE_ENGINE_MSG: onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg); break; - case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG: + case TRANSPORT_TO_DEVICE_ACTOR_MSG: case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG: case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG: case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG: diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 889630b81f..582bbedebd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -51,6 +51,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; @@ -70,6 +71,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.state.DeviceStateService; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; @@ -143,6 +145,12 @@ public abstract class BaseController { @Autowired protected EntityViewService entityViewService; + @Autowired + protected TelemetrySubscriptionService tsSubService; + + @Autowired + protected AttributesService attributesService; + @ExceptionHandler(ThingsboardException.class) public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { errorResponseHandler.handle(ex, response); @@ -251,7 +259,6 @@ public abstract class BaseController { Customer checkCustomerId(CustomerId customerId) throws ThingsboardException { try { - validateId(customerId, "Incorrect customerId " + customerId); SecurityUser authUser = getCurrentUser(); if (authUser.getAuthority() == Authority.SYS_ADMIN || (authUser.getAuthority() != Authority.TENANT_ADMIN && @@ -259,9 +266,13 @@ public abstract class BaseController { throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION, ThingsboardErrorCode.PERMISSION_DENIED); } - Customer customer = customerService.findCustomerById(customerId); - checkCustomer(customer); - return customer; + if (customerId != null && !customerId.isNullUid()) { + Customer customer = customerService.findCustomerById(customerId); + checkCustomer(customer); + return customer; + } else { + return null; + } } catch (Exception e) { throw handleException(e, false); } @@ -342,9 +353,7 @@ public abstract class BaseController { protected void checkDevice(Device device) throws ThingsboardException { checkNotNull(device); checkTenantId(device.getTenantId()); - if (device.getCustomerId() != null && !device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - checkCustomerId(device.getCustomerId()); - } + checkCustomerId(device.getCustomerId()); } protected EntityView checkEntityViewId(EntityViewId entityViewId) throws ThingsboardException { @@ -361,9 +370,7 @@ public abstract class BaseController { protected void checkEntityView(EntityView entityView) throws ThingsboardException { checkNotNull(entityView); checkTenantId(entityView.getTenantId()); - if (entityView.getCustomerId() != null && !entityView.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - checkCustomerId(entityView.getCustomerId()); - } + checkCustomerId(entityView.getCustomerId()); } Asset checkAssetId(AssetId assetId) throws ThingsboardException { @@ -380,9 +387,7 @@ public abstract class BaseController { protected void checkAsset(Asset asset) throws ThingsboardException { checkNotNull(asset); checkTenantId(asset.getTenantId()); - if (asset.getCustomerId() != null && !asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - checkCustomerId(asset.getCustomerId()); - } + checkCustomerId(asset.getCustomerId()); } Alarm checkAlarmId(AlarmId alarmId) throws ThingsboardException { @@ -491,8 +496,7 @@ public abstract class BaseController { ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException { try { log.debug("[{}] Lookup component descriptor", clazz); - ComponentDescriptor componentDescriptor = checkNotNull(componentDescriptorService.getComponent(clazz)); - return componentDescriptor; + return checkNotNull(componentDescriptorService.getComponent(clazz)); } catch (Exception e) { throw handleException(e, false); } @@ -556,16 +560,16 @@ public abstract class BaseController { } protected I emptyId(EntityType entityType) { - return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); + return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } protected void logEntityAction(I entityId, E entity, CustomerId customerId, - ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { + ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { logEntityAction(getCurrentUser(), entityId, entity, customerId, actionType, e, additionalInfo); } protected void logEntityAction(User user, I entityId, E entity, CustomerId customerId, - ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { + ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { if (customerId == null || customerId.isNullUid()) { customerId = user.getCustomerId(); } @@ -581,7 +585,7 @@ public abstract class BaseController { } private void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId, - ActionType actionType, Object... additionalInfo) { + ActionType actionType, Object... additionalInfo) { String msgType = null; switch (actionType) { case ADDED: @@ -605,6 +609,12 @@ public abstract class BaseController { case ATTRIBUTES_DELETED: msgType = DataConstants.ATTRIBUTES_DELETED; break; + case ALARM_ACK: + msgType = DataConstants.ALARM_ACK; + break; + case ALARM_CLEAR: + msgType = DataConstants.ALARM_CLEAR; + break; } if (!StringUtils.isEmpty(msgType)) { try { @@ -654,7 +664,7 @@ public abstract class BaseController { String scope = extractParameter(String.class, 0, additionalInfo); List keys = extractParameter(List.class, 1, additionalInfo); metaData.putValue("scope", scope); - ArrayNode attrsArrayNode = entityNode.putArray("attributes"); + ArrayNode attrsArrayNode = entityNode.putArray("attributes"); if (keys != null) { keys.forEach(attrsArrayNode::add); } diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index a1ba1c63f8..9b7ffd2d11 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -15,7 +15,10 @@ */ package org.thingsboard.server.controller; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -26,7 +29,9 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; @@ -34,15 +39,24 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.security.model.SecurityUser; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID; @@ -52,6 +66,7 @@ import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID; */ @RestController @RequestMapping("/api") +@Slf4j public class EntityViewController extends BaseController { public static final String ENTITY_VIEW_ID = "entityViewId"; @@ -75,6 +90,20 @@ public class EntityViewController extends BaseController { try { entityView.setTenantId(getCurrentUser().getTenantId()); EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); + List>> futures = new ArrayList<>(); + if (savedEntityView.getKeys() != null && savedEntityView.getKeys().getAttributes() != null) { + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), getCurrentUser())); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), getCurrentUser())); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), getCurrentUser())); + } + for (ListenableFuture> future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Failed to copy attributes to entity view", e); + } + } + logEntityAction(savedEntityView.getId(), savedEntityView, null, entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); return savedEntityView; @@ -85,6 +114,56 @@ public class EntityViewController extends BaseController { } } + private ListenableFuture> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection keys, SecurityUser user) throws ThingsboardException { + EntityViewId entityId = entityView.getId(); + if (keys != null && !keys.isEmpty()) { + ListenableFuture> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys); + return Futures.transform(getAttrFuture, attributeKvEntries -> { + List attributes; + if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { + attributes = + attributeKvEntries.stream() + .filter(attributeKvEntry -> { + long startTime = entityView.getStartTimeMs(); + long endTime = entityView.getEndTimeMs(); + long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); + return startTime == 0 && endTime == 0 || + (endTime == 0 && startTime < lastUpdateTs) || + (startTime == 0 && endTime > lastUpdateTs) + ? true : startTime < lastUpdateTs && endTime > lastUpdateTs; + }).collect(Collectors.toList()); + tsSubService.saveAndNotify(entityId, scope, attributes, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void tmp) { + try { + logAttributesUpdated(user, entityId, scope, attributes, null); + } catch (ThingsboardException e) { + log.error("Failed to log attribute updates", e); + } + } + + @Override + public void onFailure(Throwable t) { + try { + logAttributesUpdated(user, entityId, scope, attributes, t); + } catch (ThingsboardException e) { + log.error("Failed to log attribute updates", e); + } + } + }); + } + return null; + }); + } else { + return Futures.immediateFuture(null); + } + } + + private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List attributes, Throwable e) throws ThingsboardException { + logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_UPDATED, toException(e), + scope, attributes); + } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 7062ca97f9..6c37614147 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -59,7 +59,6 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; -import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -93,15 +92,9 @@ import java.util.stream.Collectors; @Slf4j public class TelemetryController extends BaseController { - @Autowired - private AttributesService attributesService; - @Autowired private TimeseriesService tsService; - @Autowired - private TelemetrySubscriptionService tsSubService; - @Autowired private AccessValidator accessValidator; @@ -352,7 +345,7 @@ public class TelemetryController extends BaseController { } private DeferredResult saveTelemetry(EntityId entityIdSrc, String requestBody, long ttl) throws ThingsboardException { - TelemetryUploadRequest telemetryRequest; + Map> telemetryRequest; JsonElement telemetryJson; try { telemetryJson = new JsonParser().parse(requestBody); @@ -360,12 +353,12 @@ public class TelemetryController extends BaseController { return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST); } try { - telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson); + telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson, System.currentTimeMillis()); } catch (Exception e) { return getImmediateDeferredResult("Unable to parse timeseries payload. Invalid JSON body: " + e.getMessage(), HttpStatus.BAD_REQUEST); } List entries = new ArrayList<>(); - for (Map.Entry> entry : telemetryRequest.getData().entrySet()) { + for (Map.Entry> entry : telemetryRequest.entrySet()) { for (KvEntry kv : entry.getValue()) { entries.add(new BasicTsKvEntry(entry.getKey(), kv)); } diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java index 04c4cedabd..f9caafa412 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DiscoveryService.java @@ -26,8 +26,6 @@ public interface DiscoveryService { void unpublishCurrentServer(); - String getNodeId(); - ServerInstance getCurrentServer(); List getOtherServers(); diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java index 859a7af4fc..358c8477d8 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/DummyDiscoveryService.java @@ -38,17 +38,9 @@ public class DummyDiscoveryService implements DiscoveryService { @Autowired private ServerInstanceService serverInstance; - private String nodeId; - @PostConstruct public void init() { log.info("Initializing..."); - this.nodeId = RandomStringUtils.randomAlphabetic(10); - } - - @Override - public String getNodeId() { - return nodeId; } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java index b4829f27aa..bb92642970 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java @@ -94,13 +94,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private CuratorFramework client; private PathChildrenCache cache; private String nodePath; - //TODO: make persistent? - private String nodeId; @PostConstruct public void init() { log.info("Initializing..."); - this.nodeId = RandomStringUtils.randomAlphabetic(10); Assert.hasLength(zkUrl, MiscUtils.missingProperty("zk.url")); Assert.notNull(zkRetryInterval, MiscUtils.missingProperty("zk.retry_interval_ms")); Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); @@ -183,11 +180,6 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi } } - @Override - public String getNodeId() { - return nodeId; - } - @Override public ServerInstance getCurrentServer() { return serverInstance.getSelf(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java deleted file mode 100644 index 927584789d..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultMsgQueueService.java +++ /dev/null @@ -1,111 +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.service.queue; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.transport.quota.tenant.TenantQuotaService; -import org.thingsboard.server.dao.queue.MsgQueue; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -@Service -@Slf4j -public class DefaultMsgQueueService implements MsgQueueService { - - @Value("${actors.rule.queue.max_size}") - private long queueMaxSize; - - @Value("${actors.rule.queue.cleanup_period}") - private long queueCleanUpPeriod; - - @Autowired - private MsgQueue msgQueue; - - @Autowired - private TenantQuotaService quotaService; - - private ScheduledExecutorService cleanupExecutor; - - private Map pendingCountPerTenant = new ConcurrentHashMap<>(); - - @PostConstruct - public void init() { - if (queueCleanUpPeriod > 0) { - cleanupExecutor = Executors.newSingleThreadScheduledExecutor(); - cleanupExecutor.scheduleAtFixedRate(() -> cleanup(), - queueCleanUpPeriod, queueCleanUpPeriod, TimeUnit.SECONDS); - } - } - - @PreDestroy - public void stop() { - if (cleanupExecutor != null) { - cleanupExecutor.shutdownNow(); - } - } - - @Override - public ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { - if(quotaService.isQuotaExceeded(tenantId.getId().toString())) { - log.warn("Tenant TbMsg Quota exceeded for [{}:{}] . Reject", tenantId.getId()); - return Futures.immediateFailedFuture(new RuntimeException("Tenant TbMsg Quota exceeded")); - } - - AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); - if (pendingMsgCount.incrementAndGet() < queueMaxSize) { - return msgQueue.put(tenantId, msg, nodeId, clusterPartition); - } else { - pendingMsgCount.decrementAndGet(); - return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); - } - } - - @Override - public ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { - ListenableFuture result = msgQueue.ack(tenantId, msg, nodeId, clusterPartition); - AtomicLong pendingMsgCount = pendingCountPerTenant.computeIfAbsent(tenantId, key -> new AtomicLong()); - pendingMsgCount.decrementAndGet(); - return result; - } - - @Override - public Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { - return msgQueue.findUnprocessed(tenantId, nodeId, clusterPartition); - } - - private void cleanup() { - pendingCountPerTenant.forEach((tenantId, pendingMsgCount) -> { - pendingMsgCount.set(0); - msgQueue.cleanUp(tenantId); - }); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java b/application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java deleted file mode 100644 index 2cf001ba73..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/queue/MsgQueueService.java +++ /dev/null @@ -1,32 +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.service.queue; - -import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.TbMsg; - -import java.util.UUID; - -public interface MsgQueueService { - - ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); - - ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); - - Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition); - -} diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java index 201f65613a..d33a3380f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java @@ -22,11 +22,8 @@ import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; -import java.util.Optional; - /** * Created by ashvayka on 16.04.18. */ diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 37a62117be..db31bda942 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; import org.thingsboard.server.kafka.TBKafkaProducerTemplate; import org.thingsboard.server.kafka.TbKafkaRequestTemplate; import org.thingsboard.server.kafka.TbKafkaSettings; +import org.thingsboard.server.kafka.TbNodeIdProvider; import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import javax.annotation.PostConstruct; @@ -42,7 +43,7 @@ import java.util.concurrent.ConcurrentHashMap; public class RemoteJsInvokeService extends AbstractJsInvokeService { @Autowired - private DiscoveryService discoveryService; + private TbNodeIdProvider nodeIdProvider; @Autowired private TbKafkaSettings kafkaSettings; @@ -97,15 +98,13 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder responseBuilder = TBKafkaConsumerTemplate.builder(); responseBuilder.settings(kafkaSettings); - responseBuilder.topic(responseTopicPrefix + "." + discoveryService.getNodeId()); - responseBuilder.clientId(discoveryService.getNodeId()); + responseBuilder.topic(responseTopicPrefix + "." + nodeIdProvider.getNodeId()); + responseBuilder.clientId("js-" + nodeIdProvider.getNodeId()); responseBuilder.groupId("rule-engine-node"); responseBuilder.autoCommit(true); responseBuilder.autoCommitIntervalMs(autoCommitInterval); responseBuilder.decoder(new RemoteJsResponseDecoder()); - responseBuilder.requestIdExtractor((response) -> { - return new UUID(response.getRequestIdMSB(), response.getRequestIdLSB()); - }); + responseBuilder.requestIdExtractor((response) -> new UUID(response.getRequestIdMSB(), response.getRequestIdLSB())); TbKafkaRequestTemplate.TbKafkaRequestTemplateBuilder builder = TbKafkaRequestTemplate.builder(); diff --git a/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java b/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java index 81ab9b4eb6..d4ac75374f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java +++ b/application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java @@ -36,29 +36,10 @@ public class ValidationCallback implements FutureCallback { @Override public void onSuccess(ValidationResult result) { - ValidationResultCode resultCode = result.getResultCode(); - if (resultCode == ValidationResultCode.OK) { + if (result.getResultCode() == ValidationResultCode.OK) { action.onSuccess(response); } else { - Exception e; - switch (resultCode) { - case ENTITY_NOT_FOUND: - e = new EntityNotFoundException(result.getMessage()); - break; - case UNAUTHORIZED: - e = new UnauthorizedException(result.getMessage()); - break; - case ACCESS_DENIED: - e = new AccessDeniedException(result.getMessage()); - break; - case INTERNAL_ERROR: - e = new InternalErrorException(result.getMessage()); - break; - default: - e = new UnauthorizedException("Permission denied."); - break; - } - onFailure(e); + onFailure(getException(result)); } } @@ -66,4 +47,28 @@ public class ValidationCallback implements FutureCallback { public void onFailure(Throwable e) { action.onFailure(e); } + + public static Exception getException(ValidationResult result) { + ValidationResultCode resultCode = result.getResultCode(); + Exception e; + switch (resultCode) { + case ENTITY_NOT_FOUND: + e = new EntityNotFoundException(result.getMessage()); + break; + case UNAUTHORIZED: + e = new UnauthorizedException(result.getMessage()); + break; + case ACCESS_DENIED: + e = new AccessDeniedException(result.getMessage()); + break; + case INTERNAL_ERROR: + e = new InternalErrorException(result.getMessage()); + break; + default: + e = new UnauthorizedException("Permission denied."); + break; + } + return e; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index bcca43ca5e..3b0deea78a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -143,7 +143,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio public void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub) { long startTime = 0L; long endTime = 0L; - if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW) && TelemetryFeature.TIMESERIES.equals(sub.getType())) { EntityView entityView = entityViewService.findEntityViewById(new EntityViewId(entityId.getId())); entityId = entityView.getEntityId(); startTime = entityView.getStartTimeMs(); @@ -165,38 +165,15 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio } private SubscriptionState getUpdatedSubscriptionState(EntityId entityId, SubscriptionState sub, EntityView entityView) { - boolean allKeys; Map keyStates; - if (sub.getType().equals(TelemetryFeature.TIMESERIES) && !entityView.getKeys().getTimeseries().isEmpty()) { - allKeys = false; + if(sub.isAllKeys()) { + keyStates = entityView.getKeys().getTimeseries().stream().collect(Collectors.toMap(k -> k, k -> 0L)); + } else { keyStates = sub.getKeyStates().entrySet() .stream().filter(entry -> entityView.getKeys().getTimeseries().contains(entry.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } else if (sub.getType().equals(TelemetryFeature.ATTRIBUTES)) { - if (sub.getScope().equals(DataConstants.CLIENT_SCOPE) && !entityView.getKeys().getAttributes().getCs().isEmpty()) { - allKeys = false; - keyStates = filterMap(sub, entityView.getKeys().getAttributes().getCs()); - } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSs().isEmpty()) { - allKeys = false; - keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSs()); - } else if (sub.getScope().equals(DataConstants.SERVER_SCOPE) && !entityView.getKeys().getAttributes().getSh().isEmpty()) { - allKeys = false; - keyStates = filterMap(sub, entityView.getKeys().getAttributes().getSh()); - } else { - allKeys = sub.isAllKeys(); - keyStates = sub.getKeyStates(); - } - } else { - allKeys = sub.isAllKeys(); - keyStates = sub.getKeyStates(); } - return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), allKeys, keyStates, sub.getScope()); - } - - private Map filterMap(SubscriptionState sub, List allowedKeys) { - return sub.getKeyStates().entrySet() - .stream().filter(entry -> allowedKeys.contains(entry.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + return new SubscriptionState(sub.getWsSessionId(), sub.getSubscriptionId(), entityId, sub.getType(), false, keyStates, sub.getScope()); } @Override @@ -467,7 +444,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio onLocalSubUpdate(entityId, s -> TelemetryFeature.ATTRIBUTES == s.getType() && (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope())), s -> { List subscriptionUpdate = null; for (AttributeKvEntry kv : attributes) { - if (isInTimeRange(s, kv.getLastUpdateTs()) && (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey()))) { + if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) { if (subscriptionUpdate == null) { subscriptionUpdate = new ArrayList<>(); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java index 2ff8e89070..1251ca3b7e 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java @@ -37,13 +37,18 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.service.security.AccessValidator; +import org.thingsboard.server.service.security.ValidationCallback; import org.thingsboard.server.service.security.ValidationResult; +import org.thingsboard.server.service.security.ValidationResultCode; import org.thingsboard.server.service.telemetry.cmd.AttributesSubscriptionCmd; import org.thingsboard.server.service.telemetry.cmd.GetHistoryCmd; import org.thingsboard.server.service.telemetry.cmd.SubscriptionCmd; import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmd; import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.telemetry.cmd.TimeseriesSubscriptionCmd; +import org.thingsboard.server.service.telemetry.exception.AccessDeniedException; +import org.thingsboard.server.service.telemetry.exception.EntityNotFoundException; +import org.thingsboard.server.service.telemetry.exception.InternalErrorException; import org.thingsboard.server.service.telemetry.exception.UnauthorizedException; import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; import org.thingsboard.server.service.telemetry.sub.SubscriptionState; @@ -535,11 +540,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi }; } - private FutureCallback on(Consumer success, Consumer failure) { + private FutureCallback on(Consumer success, Consumer failure) { return new FutureCallback() { @Override public void onSuccess(@Nullable ValidationResult result) { - success.accept(result); + ValidationResultCode resultCode = result.getResultCode(); + if (resultCode == ValidationResultCode.OK) { + success.accept(null); + } else { + onFailure(ValidationCallback.getException(result)); + } } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java new file mode 100644 index 0000000000..5c39f8af4d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java @@ -0,0 +1,173 @@ +/** + * 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.transport; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; +import org.thingsboard.server.kafka.TBKafkaProducerTemplate; +import org.thingsboard.server.kafka.TbKafkaResponseTemplate; +import org.thingsboard.server.kafka.TbKafkaSettings; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.state.DeviceStateService; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Created by ashvayka on 05.10.18. + */ +@Slf4j +@Service +public class LocalTransportApiService implements TransportApiService { + + private static final ObjectMapper mapper = new ObjectMapper(); + + @Autowired + private DeviceService deviceService; + + @Autowired + private RelationService relationService; + + @Autowired + private DeviceCredentialsService deviceCredentialsService; + + @Autowired + private DeviceStateService deviceStateService; + + @Autowired + private DbCallbackExecutorService dbCallbackExecutorService; + + private ReentrantLock deviceCreationLock = new ReentrantLock(); + + @Override + public ListenableFuture handle(TransportApiRequestMsg transportApiRequestMsg) { + if (transportApiRequestMsg.hasValidateTokenRequestMsg()) { + ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg(); + return validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN); + } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { + ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); + return validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); + } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { + return handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); + } + return getEmptyTransportApiResponseFuture(); + } + + private ListenableFuture validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) { + //TODO: Make async and enable caching + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId); + if (credentials != null && credentials.getCredentialsType() == credentialsType) { + return getDeviceInfo(credentials.getDeviceId()); + } else { + return getEmptyTransportApiResponseFuture(); + } + } + + private ListenableFuture handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { + DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB())); + ListenableFuture gatewayFuture = deviceService.findDeviceByIdAsync(gatewayId); + return Futures.transform(gatewayFuture, gateway -> { + deviceCreationLock.lock(); + try { + Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName()); + if (device == null) { + device = new Device(); + device.setTenantId(gateway.getTenantId()); + device.setName(requestMsg.getDeviceName()); + device.setType(requestMsg.getDeviceType()); + device.setCustomerId(gateway.getCustomerId()); + device = deviceService.saveDevice(device); + relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created")); + deviceStateService.onDeviceAdded(device); + } + return TransportApiResponseMsg.newBuilder() + .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); + } catch (JsonProcessingException e) { + log.warn("[{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e); + throw new RuntimeException(e); + } finally { + deviceCreationLock.unlock(); + } + }, dbCallbackExecutorService); + } + + + private ListenableFuture getDeviceInfo(DeviceId deviceId) { + return Futures.transform(deviceService.findDeviceByIdAsync(deviceId), device -> { + if (device == null) { + log.trace("[{}] Failed to lookup device by id", deviceId); + return getEmptyTransportApiResponse(); + } + try { + return TransportApiResponseMsg.newBuilder() + .setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); + } catch (JsonProcessingException e) { + log.warn("[{}] Failed to lookup device by id", deviceId, e); + return getEmptyTransportApiResponse(); + } + }); + } + + private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { + return DeviceInfoProto.newBuilder() + .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) + .setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) + .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) + .setDeviceName(device.getName()) + .setDeviceType(device.getType()) + .setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo())) + .build(); + } + + private ListenableFuture getEmptyTransportApiResponseFuture() { + return Futures.immediateFuture(getEmptyTransportApiResponse()); + } + + private TransportApiResponseMsg getEmptyTransportApiResponse() { + return TransportApiResponseMsg.newBuilder() + .setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java new file mode 100644 index 0000000000..0f722306ff --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java @@ -0,0 +1,225 @@ +/** + * 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.transport; + +import akka.actor.ActorRef; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.util.DonAsynchron; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.service.AbstractTransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; +import org.thingsboard.server.service.encoding.DataDecodingEncodingService; +import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Created by ashvayka on 12.10.18. + */ +@Slf4j +@Service +@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "local") +public class LocalTransportService extends AbstractTransportService implements RuleEngineTransportService { + + @Autowired + private TransportApiService transportApiService; + + @Autowired + private ActorSystemContext actorContext; + + //TODO: completely replace this routing with the Kafka routing by partition ids. + @Autowired + private ClusterRoutingService routingService; + @Autowired + private ClusterRpcService rpcService; + @Autowired + private DataDecodingEncodingService encodingService; + + @PostConstruct + public void init() { + super.init(); + } + + @PreDestroy + public void destroy() { + super.destroy(); + } + + @Override + public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { + DonAsynchron.withCallback( + transportApiService.handle(TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()), + transportApiResponseMsg -> { + if (callback != null) { + callback.onSuccess(transportApiResponseMsg.getValidateTokenResponseMsg()); + } + }, + getThrowableConsumer(callback), transportCallbackExecutor); + } + + @Override + public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + DonAsynchron.withCallback( + transportApiService.handle(TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()), + transportApiResponseMsg -> { + if (callback != null) { + callback.onSuccess(transportApiResponseMsg.getValidateTokenResponseMsg()); + } + }, + getThrowableConsumer(callback), transportCallbackExecutor); + } + + @Override + public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback) { + DonAsynchron.withCallback( + transportApiService.handle(TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()), + transportApiResponseMsg -> { + if (callback != null) { + callback.onSuccess(transportApiResponseMsg.getGetOrCreateDeviceResponseMsg()); + } + }, + getThrowableConsumer(callback), transportCallbackExecutor); + } + + @Override + public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSessionEvent(msg).build(), callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostTelemetry(msg).build(), callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostAttributes(msg).build(), callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setGetAttributes(msg).build(), callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToServerRPCCallRequest(msg).build(), callback); + } + } + + @Override + public void process(String nodeId, DeviceActorToTransportMsg msg) { + process(nodeId, msg, null, null); + } + + @Override + public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer onFailure) { + processToTransportMsg(msg); + if (onSuccess != null) { + onSuccess.run(); + } + } + + private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { + TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg); + Optional address = routingService.resolveById(wrapper.getDeviceId()); + if (address.isPresent()) { + rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper)); + } else { + actorContext.getAppActor().tell(wrapper, ActorRef.noSender()); + } + if (callback != null) { + callback.onSuccess(null); + } + } + + private Consumer getThrowableConsumer(TransportServiceCallback callback) { + return e -> { + if (callback != null) { + callback.onError(e); + } + }; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java new file mode 100644 index 0000000000..38db1a415b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java @@ -0,0 +1,200 @@ +/** + * 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.transport; + +import akka.actor.ActorRef; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.producer.Callback; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ActorService; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; +import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; +import org.thingsboard.server.kafka.TBKafkaProducerTemplate; +import org.thingsboard.server.kafka.TbKafkaSettings; +import org.thingsboard.server.kafka.TbNodeIdProvider; +import org.thingsboard.server.service.cluster.discovery.DiscoveryService; +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; +import org.thingsboard.server.service.encoding.DataDecodingEncodingService; +import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +/** + * Created by ashvayka on 09.10.18. + */ +@Slf4j +@Service +@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "remote") +public class RemoteRuleEngineTransportService implements RuleEngineTransportService { + + @Value("${transport.remote.rule_engine.topic}") + private String ruleEngineTopic; + @Value("${transport.remote.notifications.topic}") + private String notificationsTopic; + @Value("${transport.remote.rule_engine.poll_interval}") + private int pollDuration; + @Value("${transport.remote.rule_engine.auto_commit_interval}") + private int autoCommitInterval; + + @Autowired + private TbKafkaSettings kafkaSettings; + + @Autowired + private TbNodeIdProvider nodeIdProvider; + + @Autowired + private ActorSystemContext actorContext; + + //TODO: completely replace this routing with the Kafka routing by partition ids. + @Autowired + private ClusterRoutingService routingService; + @Autowired + private ClusterRpcService rpcService; + @Autowired + private DataDecodingEncodingService encodingService; + + private TBKafkaConsumerTemplate ruleEngineConsumer; + private TBKafkaProducerTemplate notificationsProducer; + + private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(); + + private volatile boolean stopped = false; + + @PostConstruct + public void init() { + TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder notificationsProducerBuilder = TBKafkaProducerTemplate.builder(); + notificationsProducerBuilder.settings(kafkaSettings); + notificationsProducerBuilder.defaultTopic(notificationsTopic); + notificationsProducerBuilder.encoder(new ToTransportMsgEncoder()); + + notificationsProducer = notificationsProducerBuilder.build(); + notificationsProducer.init(); + + TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder ruleEngineConsumerBuilder = TBKafkaConsumerTemplate.builder(); + ruleEngineConsumerBuilder.settings(kafkaSettings); + ruleEngineConsumerBuilder.topic(ruleEngineTopic); + ruleEngineConsumerBuilder.clientId("transport-" + nodeIdProvider.getNodeId()); + ruleEngineConsumerBuilder.groupId("tb-node"); + ruleEngineConsumerBuilder.autoCommit(true); + ruleEngineConsumerBuilder.autoCommitIntervalMs(autoCommitInterval); + ruleEngineConsumerBuilder.decoder(new ToRuleEngineMsgDecoder()); + + ruleEngineConsumer = ruleEngineConsumerBuilder.build(); + ruleEngineConsumer.subscribe(); + + mainConsumerExecutor.execute(() -> { + while (!stopped) { + try { + ConsumerRecords records = ruleEngineConsumer.poll(Duration.ofMillis(pollDuration)); + records.forEach(record -> { + try { + ToRuleEngineMsg toRuleEngineMsg = ruleEngineConsumer.decode(record); + if (toRuleEngineMsg.hasToDeviceActorMsg()) { + forwardToDeviceActor(toRuleEngineMsg.getToDeviceActorMsg()); + } + } catch (Throwable e) { + log.warn("Failed to process the notification.", e); + } + }); + } catch (Exception e) { + log.warn("Failed to obtain messages from queue.", e); + try { + Thread.sleep(pollDuration); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new requests", e2); + } + } + } + }); + } + + @Override + public void process(String nodeId, DeviceActorToTransportMsg msg) { + process(nodeId, msg, null, null); + } + + @Override + public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer onFailure) { + notificationsProducer.send(notificationsTopic + "." + nodeId, + new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()).toString(), + ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build() + , new QueueCallbackAdaptor(onSuccess, onFailure)); + } + + private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg) { + TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg); + Optional address = routingService.resolveById(wrapper.getDeviceId()); + if (address.isPresent()) { + rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper)); + } else { + actorContext.getAppActor().tell(wrapper, ActorRef.noSender()); + } + } + + @PreDestroy + public void destroy() { + stopped = true; + if (ruleEngineConsumer != null) { + ruleEngineConsumer.unsubscribe(); + } + if (mainConsumerExecutor != null) { + mainConsumerExecutor.shutdownNow(); + } + } + + private static class QueueCallbackAdaptor implements Callback { + private final Runnable onSuccess; + private final Consumer onFailure; + + QueueCallbackAdaptor(Runnable onSuccess, Consumer onFailure) { + this.onSuccess = onSuccess; + this.onFailure = onFailure; + } + + @Override + public void onCompletion(RecordMetadata metadata, Exception exception) { + if (exception == null) { + if (onSuccess != null) { + onSuccess.run(); + } + } else { + if (onFailure != null) { + onFailure.accept(exception); + } + } + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java new file mode 100644 index 0000000000..6d422f4dd6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java @@ -0,0 +1,109 @@ +/** + * 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.transport; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.kafka.*; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Created by ashvayka on 05.10.18. + */ +@Slf4j +@Component +@ConditionalOnProperty(prefix = "transport", value = "type", havingValue = "remote") +public class RemoteTransportApiService { + + @Value("${transport.remote.transport_api.requests_topic}") + private String transportApiRequestsTopic; + @Value("${transport.remote.transport_api.responses_topic}") + private String transportApiResponsesTopic; + @Value("${transport.remote.transport_api.max_pending_requests}") + private int maxPendingRequests; + @Value("${transport.remote.transport_api.request_timeout}") + private long requestTimeout; + @Value("${transport.remote.transport_api.request_poll_interval}") + private int responsePollDuration; + @Value("${transport.remote.transport_api.request_auto_commit_interval}") + private int autoCommitInterval; + + @Autowired + private TbKafkaSettings kafkaSettings; + + @Autowired + private TbNodeIdProvider nodeIdProvider; + + @Autowired + private TransportApiService transportApiService; + + private ExecutorService transportCallbackExecutor; + + private TbKafkaResponseTemplate transportApiTemplate; + + @PostConstruct + public void init() { + this.transportCallbackExecutor = new ThreadPoolExecutor(0, 100, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); + + TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder responseBuilder = TBKafkaProducerTemplate.builder(); + responseBuilder.settings(kafkaSettings); + responseBuilder.defaultTopic(transportApiResponsesTopic); + responseBuilder.encoder(new TransportApiResponseEncoder()); + + TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder requestBuilder = TBKafkaConsumerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.topic(transportApiRequestsTopic); + requestBuilder.clientId(nodeIdProvider.getNodeId()); + requestBuilder.groupId("tb-node"); + requestBuilder.autoCommit(true); + requestBuilder.autoCommitIntervalMs(autoCommitInterval); + requestBuilder.decoder(new TransportApiRequestDecoder()); + + TbKafkaResponseTemplate.TbKafkaResponseTemplateBuilder + builder = TbKafkaResponseTemplate.builder(); + builder.requestTemplate(requestBuilder.build()); + builder.responseTemplate(responseBuilder.build()); + builder.maxPendingRequests(maxPendingRequests); + builder.requestTimeout(requestTimeout); + builder.pollInterval(responsePollDuration); + builder.executor(transportCallbackExecutor); + builder.handler(transportApiService); + transportApiTemplate = builder.build(); + transportApiTemplate.init(); + } + + @PreDestroy + public void destroy() { + if (transportApiTemplate != null) { + transportApiTemplate.stop(); + } + if (transportCallbackExecutor != null) { + transportCallbackExecutor.shutdownNow(); + } + } + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java b/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineTransportService.java similarity index 59% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java rename to application/src/main/java/org/thingsboard/server/service/transport/RuleEngineTransportService.java index 4fcde4456b..8ca484ffcf 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicRequest.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/RuleEngineTransportService.java @@ -13,24 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.core; +package org.thingsboard.server.service.transport; -import java.io.Serializable; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; + +import java.util.function.Consumer; /** - * @author Andrew Shvayka + * Created by ashvayka on 05.10.18. */ -public class BasicRequest implements Serializable { - - public static final Integer DEFAULT_REQUEST_ID = 0; +public interface RuleEngineTransportService { - private final Integer requestId; + void process(String nodeId, DeviceActorToTransportMsg msg); - public BasicRequest(Integer requestId) { - this.requestId = requestId; - } + void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer onFailure); - public Integer getRequestId() { - return requestId; - } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java b/application/src/main/java/org/thingsboard/server/service/transport/ToRuleEngineMsgDecoder.java similarity index 59% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java rename to application/src/main/java/org/thingsboard/server/service/transport/ToRuleEngineMsgDecoder.java index f8f24e8d20..16954eea10 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/ToRuleEngineMsgDecoder.java @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.core; +package org.thingsboard.server.service.transport; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.kafka.TbKafkaDecoder; + +import java.io.IOException; /** - * @author Andrew Shvayka + * Created by ashvayka on 05.10.18. */ -public class RpcSubscribeMsg implements FromDeviceMsg { +public class ToRuleEngineMsgDecoder implements TbKafkaDecoder { @Override - public SessionMsgType getMsgType() { - return SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + public ToRuleEngineMsg decode(byte[] data) throws IOException { + return ToRuleEngineMsg.parseFrom(data); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java b/application/src/main/java/org/thingsboard/server/service/transport/ToTransportMsgEncoder.java similarity index 63% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java rename to application/src/main/java/org/thingsboard/server/service/transport/ToTransportMsgEncoder.java index cadaf3ce56..bb0f5b620e 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/ToTransportMsgEncoder.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.core; +package org.thingsboard.server.service.transport; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.kafka.TbKafkaEncoder; /** - * @author Andrew Shvayka + * Created by ashvayka on 05.10.18. */ -public class AttributesSubscribeMsg implements FromDeviceMsg { +public class ToTransportMsgEncoder implements TbKafkaEncoder { @Override - public SessionMsgType getMsgType() { - return SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + public byte[] encode(ToTransportMsg value) { + return value.toByteArray(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TransportApiRequestDecoder.java b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiRequestDecoder.java new file mode 100644 index 0000000000..c54f1a0303 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiRequestDecoder.java @@ -0,0 +1,31 @@ +/** + * 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.transport; + +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.kafka.TbKafkaDecoder; + +import java.io.IOException; + +/** + * Created by ashvayka on 05.10.18. + */ +public class TransportApiRequestDecoder implements TbKafkaDecoder { + @Override + public TransportApiRequestMsg decode(byte[] data) throws IOException { + return TransportApiRequestMsg.parseFrom(data); + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiResponseEncoder.java similarity index 61% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java rename to application/src/main/java/org/thingsboard/server/service/transport/TransportApiResponseEncoder.java index 6d3ad5e8be..175a92df96 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiResponseEncoder.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.core; +package org.thingsboard.server.service.transport; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.kafka.TbKafkaEncoder; + +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; /** - * @author Andrew Shvayka + * Created by ashvayka on 05.10.18. */ -public class SessionCloseMsg implements FromDeviceMsg { +public class TransportApiResponseEncoder implements TbKafkaEncoder { @Override - public SessionMsgType getMsgType() { - return SessionMsgType.SESSION_CLOSE; + public byte[] encode(TransportApiResponseMsg value) { + return value.toByteArray(); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/SessionAwareMsg.java b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java similarity index 63% rename from common/message/src/main/java/org/thingsboard/server/common/msg/aware/SessionAwareMsg.java rename to application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java index db6ff6f632..cc6f5b9f15 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/SessionAwareMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/TransportApiService.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.aware; +package org.thingsboard.server.service.transport; -import org.thingsboard.server.common.data.id.SessionId; - -public interface SessionAwareMsg { - - SessionId getSessionId(); +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.kafka.TbKafkaHandler; +/** + * Created by ashvayka on 05.10.18. + */ +public interface TransportApiService extends TbKafkaHandler { } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java new file mode 100644 index 0000000000..570082d542 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java @@ -0,0 +1,50 @@ +/** + * 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.transport.msg; + +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; +import org.thingsboard.server.common.msg.aware.TenantAwareMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; + +import java.io.Serializable; +import java.util.UUID; + +/** + * Created by ashvayka on 09.10.18. + */ +@Data +public class TransportToDeviceActorMsgWrapper implements TbActorMsg, DeviceAwareMsg, TenantAwareMsg, Serializable { + + private final TenantId tenantId; + private final DeviceId deviceId; + private final TransportToDeviceActorMsg msg; + + public TransportToDeviceActorMsgWrapper(TransportToDeviceActorMsg msg) { + this.msg = msg; + this.tenantId = new TenantId(new UUID(msg.getSessionInfo().getTenantIdMSB(), msg.getSessionInfo().getTenantIdLSB())); + this.deviceId = new DeviceId(new UUID(msg.getSessionInfo().getDeviceIdMSB(), msg.getSessionInfo().getDeviceIdLSB())); + } + + @Override + public MsgType getMsgType() { + return MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG; + } +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8d75dab4a0..8d65e3dd33 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -82,47 +82,6 @@ dashboard: # Maximum allowed datapoints fetched by widgets max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}" -# Device communication protocol parameters -http: - request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" - -# MQTT server parameters -mqtt: - # Enable/disable mqtt transport protocol. - enabled: "${MQTT_ENABLED:true}" - bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" - bind_port: "${MQTT_BIND_PORT:1883}" - adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}" - timeout: "${MQTT_TIMEOUT:10000}" - netty: - leak_detector_level: "${NETTY_LEASK_DETECTOR_LVL:DISABLED}" - boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" - worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" - max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" - # MQTT SSL configuration - ssl: - # Enable/disable SSL support - enabled: "${MQTT_SSL_ENABLED:false}" - # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext - protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" - # Path to the key store that holds the SSL certificate - key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" - # Password used to access the key store - key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" - # Password used to access the key - key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" - # Type of the key store - key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" - -# CoAP server parameters -coap: - # Enable/disable coap transport protocol. - enabled: "${COAP_ENABLED:false}" - bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" - bind_port: "${COAP_BIND_PORT:5683}" - adaptor: "${COAP_ADAPTOR_NAME:JsonCoapAdaptor}" - timeout: "${COAP_TIMEOUT:10000}" - #Quota parameters quota: host: @@ -154,7 +113,7 @@ quota: # Interval for scheduled task that cleans expired records. TTL is used for expiring cleanPeriodMs: "${QUOTA_TENANT_CLEAN_PERIOD_MS:300000}" # Enable Host API Limits - enabled: "${QUOTA_TENANT_ENABLED:false}" + enabled: "${QUOTA_TENANT_ENABLED:true}" # Array of whitelist tenants whitelist: "${QUOTA_TENANT_WHITELIST:}" # Array of blacklist tenants @@ -223,12 +182,6 @@ cassandra: permit_max_wait_time: "${PERMIT_MAX_WAIT_TIME:120000}" rate_limit_print_interval_ms: "${CASSANDRA_QUERY_RATE_LIMIT_PRINT_MS:30000}" - queue: - msg.ttl: 604800 # 7 days - ack.ttl: 604800 # 7 days - partitions.ttl: 604800 # 7 days - partitioning: "HOURS" - # SQL configuration parameters sql: # Specify executor service type used to perform timeseries insert tasks: SINGLE FIXED CACHED @@ -262,13 +215,6 @@ actors: node: # Errors for particular actor are persisted once per specified amount of milliseconds error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}" - queue: - # Message queue type - type: "${ACTORS_RULE_QUEUE_TYPE:memory}" - # Message queue maximum size (per tenant) - max_size: "${ACTORS_RULE_QUEUE_MAX_SIZE:100}" - # Message queue cleanup period in seconds - cleanup_period: "${ACTORS_RULE_QUEUE_CLEANUP_PERIOD:3600}" statistics: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" @@ -427,6 +373,19 @@ kafka: batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" + transport_api: + requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" + responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" + max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" + request_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + request_poll_interval: "${TB_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}" + request_auto_commit_interval: "${TB_TRANSPORT_REQUEST_AUTO_COMMIT_INTERVAL_MS:100}" + rule_engine: + topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}" + poll_interval: "${TB_RULE_ENGINE_POLL_INTERVAL_MS:25}" + auto_commit_interval: "${TB_RULE_ENGINE_AUTO_COMMIT_INTERVAL_MS:100}" + notifications: + topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" js: evaluator: "${JS_EVALUATOR:local}" # local/remote @@ -456,3 +415,57 @@ js: response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}" + +transport: + type: "${TRANSPORT_TYPE:local}" # local or remote + remote: + transport_api: + requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" + responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" + max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" + request_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + request_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" + request_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:1000}" + rule_engine: + topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}" + poll_interval: "${TB_RULE_ENGINE_POLL_INTERVAL_MS:25}" + auto_commit_interval: "${TB_RULE_ENGINE_AUTO_COMMIT_INTERVAL_MS:100}" + notifications: + topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" + # Local HTTP transport parameters + http: + enabled: "${HTTP_ENABLED:true}" + request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" + # Local MQTT transport parameters + mqtt: + # Enable/disable mqtt transport protocol. + enabled: "${MQTT_ENABLED:true}" + bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" + bind_port: "${MQTT_BIND_PORT:1883}" + timeout: "${MQTT_TIMEOUT:10000}" + netty: + leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" + boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" + worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" + # MQTT SSL configuration + ssl: + # Enable/disable SSL support + enabled: "${MQTT_SSL_ENABLED:false}" + # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext + protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" + # Path to the key store that holds the SSL certificate + key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" + # Password used to access the key store + key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" + # Type of the key store + key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" + # Local CoAP transport parameters + coap: + # Enable/disable coap transport protocol. + enabled: "${COAP_ENABLED:true}" + bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" + bind_port: "${COAP_BIND_PORT:5683}" + timeout: "${COAP_TIMEOUT:10000}" diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java index 1b042a821c..cb31a4b50c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java @@ -27,9 +27,7 @@ import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.dao.queue.MsgQueue; import org.thingsboard.server.dao.rule.RuleChainService; -import org.thingsboard.server.service.queue.MsgQueueService; import java.io.IOException; import java.util.function.Predicate; @@ -42,9 +40,6 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest { @Autowired protected RuleChainService ruleChainService; - @Autowired - protected MsgQueueService msgQueueService; - protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception { return doPost("/api/ruleChain", ruleChain, RuleChain.class); } diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index c86d49609e..f050f3b505 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -191,9 +191,6 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText()); - - List unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), ruleChain.getId().getId(), 0L)); - Assert.assertEquals(0, unAckMsgList.size()); } @Test @@ -311,12 +308,6 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText()); - - List unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), rootRuleChain.getId().getId(), 0L)); - Assert.assertEquals(0, unAckMsgList.size()); - - unAckMsgList = Lists.newArrayList(msgQueueService.findUnprocessed(savedTenant.getId(), secondaryRuleChain.getId().getId(), 0L)); - Assert.assertEquals(0, unAckMsgList.size()); } } diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 7ac0789383..24db4570bd 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -162,73 +162,4 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText()); } - @Test - public void testRuleChainWithOneRuleAndMsgFromQueue() throws Exception { - // Creating Rule Chain - RuleChain ruleChain = new RuleChain(); - ruleChain.setName("Simple Rule Chain"); - ruleChain.setTenantId(savedTenant.getId()); - ruleChain.setRoot(true); - ruleChain.setDebugMode(true); - ruleChain = saveRuleChain(ruleChain); - Assert.assertNull(ruleChain.getFirstRuleNodeId()); - - // Saving the device - Device device = new Device(); - device.setName("My device"); - device.setType("default"); - device = doPost("/api/device", device, Device.class); - - attributesService.save(device.getId(), DataConstants.SERVER_SCOPE, - Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis()))); - - // Pushing Message to the system - TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), - "CUSTOM", - device.getId(), - new TbMsgMetaData(), - "{}", - ruleChain.getId(), null, 0L); - msgQueueService.put(device.getTenantId(), tbMsg, ruleChain.getId().getId(), 0L); - - Thread.sleep(1000); - - RuleChainMetaData metaData = new RuleChainMetaData(); - metaData.setRuleChainId(ruleChain.getId()); - - RuleNode ruleNode = new RuleNode(); - ruleNode.setName("Simple Rule Node"); - ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); - ruleNode.setDebugMode(true); - TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); - configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey")); - ruleNode.setConfiguration(mapper.valueToTree(configuration)); - - metaData.setNodes(Collections.singletonList(ruleNode)); - metaData.setFirstNodeIndex(0); - - metaData = saveRuleChainMetaData(metaData); - Assert.assertNotNull(metaData); - - ruleChain = getRuleChain(ruleChain.getId()); - Assert.assertNotNull(ruleChain.getFirstRuleNodeId()); - - Thread.sleep(3000); - - TimePageData eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); - List events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); - - Assert.assertEquals(2, events.size()); - - Event inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); - Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId()); - Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText()); - - Event outEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get(); - Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId()); - Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); - - Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText()); - } - } diff --git a/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java index 5e6896b2b7..1ebccadfea 100644 --- a/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java +++ b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java @@ -57,7 +57,7 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { @Test public void testGetAttributes() throws Exception { doGetAsync("/api/v1/" + "WRONG_TOKEN" + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isUnauthorized()); - doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isNotFound()); + doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk()); Map attrMap = new HashMap<>(); attrMap.put("keyA", "valueA"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 34c14deb8c..307c59bdb9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -57,6 +57,8 @@ public class DataConstants { public static final String ENTITY_UNASSIGNED = "ENTITY_UNASSIGNED"; public static final String ATTRIBUTES_UPDATED = "ATTRIBUTES_UPDATED"; public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED"; + public static final String ALARM_ACK = "ALARM_ACK"; + public static final String ALARM_CLEAR = "ALARM_CLEAR"; public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; diff --git a/common/message/pom.xml b/common/message/pom.xml index f4f20b08c6..d914d4f21d 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -40,6 +40,10 @@ org.thingsboard.common data + + org.bouncycastle + bcprov-jdk15on + org.slf4j slf4j-api diff --git a/dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java similarity index 97% rename from dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java index a44ad57e19..41a608763a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/EncryptionUtil.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/EncryptionUtil.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao; +package org.thingsboard.server.common.msg; import lombok.extern.slf4j.Slf4j; import org.bouncycastle.crypto.digests.SHA3Digest; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java index 60e5469fa5..24758b5b36 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java @@ -77,11 +77,6 @@ public enum MsgType { */ RULE_TO_SELF_MSG, - /** - * Message that is sent by Session Actor to Device Actor. Represents messages from the device itself. - */ - DEVICE_SESSION_TO_DEVICE_ACTOR_MSG, - DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG, DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG, @@ -96,8 +91,6 @@ public enum MsgType { DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG, - DEVICE_ACTOR_QUEUE_TIMEOUT_MSG, - /** * Message that is sent from the Device Actor to Rule Engine. Requires acknowledgement */ @@ -106,11 +99,16 @@ public enum MsgType { /** * Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue. */ - RULE_ENGINE_QUEUE_PUT_ACK_MSG, ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG, TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG, SESSION_TIMEOUT_MSG, SESSION_CTRL_MSG, - STATS_PERSIST_TICK_MSG; + STATS_PERSIST_TICK_MSG, + + + /** + * Message that is sent by TransportRuleEngineService to Device Actor. Represents messages from the device itself. + */ + TRANSPORT_TO_DEVICE_ACTOR_MSG; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ActorSystemToDeviceSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ActorSystemToDeviceSessionActorMsg.java deleted file mode 100644 index 6bb5c9911d..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ActorSystemToDeviceSessionActorMsg.java +++ /dev/null @@ -1,30 +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.common.msg.core; - -import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; - -import java.io.Serializable; - -/** - * @author Andrew Shvayka - */ -public interface ActorSystemToDeviceSessionActorMsg extends SessionAwareMsg, Serializable, TbActorMsg { - - ToDeviceMsg getMsg(); -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java deleted file mode 100644 index 1f0f9ddc99..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java +++ /dev/null @@ -1,47 +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.common.msg.core; - -import lombok.ToString; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; - -@ToString -public class AttributesUpdateNotification implements ToDeviceMsg { - - private static final long serialVersionUID = 1L; - - private AttributesKVMsg data; - - public AttributesUpdateNotification(AttributesKVMsg data) { - this.data = data; - } - - @Override - public boolean isSuccess() { - return true; - } - - public SessionMsgType getSessionMsgType() { - return SessionMsgType.ATTRIBUTES_UPDATE_NOTIFICATION; - } - - public AttributesKVMsg getData() { - return data; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateRequest.java deleted file mode 100644 index d4cb4b1ae6..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateRequest.java +++ /dev/null @@ -1,28 +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.common.msg.core; - -import java.util.Set; - -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; - -public interface AttributesUpdateRequest extends FromDeviceRequestMsg { - - Set getAttributes(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicActorSystemToDeviceSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicActorSystemToDeviceSessionActorMsg.java deleted file mode 100644 index cc298915c8..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicActorSystemToDeviceSessionActorMsg.java +++ /dev/null @@ -1,52 +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.common.msg.core; - -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; - -public class BasicActorSystemToDeviceSessionActorMsg implements ActorSystemToDeviceSessionActorMsg { - - private final ToDeviceMsg msg; - private final SessionId sessionId; - - public BasicActorSystemToDeviceSessionActorMsg(ToDeviceMsg msg, SessionId sessionId) { - super(); - this.msg = msg; - this.sessionId = sessionId; - } - - @Override - public SessionId getSessionId() { - return sessionId; - } - - @Override - public ToDeviceMsg getMsg() { - return msg; - } - - @Override - public String toString() { - return "BasicActorSystemToDeviceSessionActorMsg [msg=" + msg + ", sessionId=" + sessionId + "]"; - } - - @Override - public MsgType getMsgType() { - return MsgType.ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicAttributesUpdateRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicAttributesUpdateRequest.java deleted file mode 100644 index 76efca0f07..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicAttributesUpdateRequest.java +++ /dev/null @@ -1,63 +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.common.msg.core; - -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -public class BasicAttributesUpdateRequest extends BasicRequest implements AttributesUpdateRequest { - - private static final long serialVersionUID = 1L; - - private final Set data; - - public BasicAttributesUpdateRequest() { - this(DEFAULT_REQUEST_ID); - } - - public BasicAttributesUpdateRequest(Integer requestId) { - super(requestId); - this.data = new LinkedHashSet<>(); - } - - public void add(AttributeKvEntry entry) { - this.data.add(entry); - } - - public void add(Collection entries) { - this.data.addAll(entries); - } - - @Override - public SessionMsgType getMsgType() { - return SessionMsgType.POST_ATTRIBUTES_REQUEST; - } - - @Override - public Set getAttributes() { - return data; - } - - @Override - public String toString() { - return "BasicAttributesUpdateRequest [data=" + data + "]"; - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java deleted file mode 100644 index 3ee04ba216..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java +++ /dev/null @@ -1,45 +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.common.msg.core; - -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -public class BasicCommandAckResponse extends BasicResponseMsg implements StatusCodeResponse { - - private static final long serialVersionUID = 1L; - - public static BasicCommandAckResponse onSuccess(SessionMsgType requestMsgType, Integer requestId) { - return BasicCommandAckResponse.onSuccess(requestMsgType, requestId, 200); - } - - public static BasicCommandAckResponse onSuccess(SessionMsgType requestMsgType, Integer requestId, Integer code) { - return new BasicCommandAckResponse(requestMsgType, requestId, true, null, code); - } - - public static BasicCommandAckResponse onError(SessionMsgType requestMsgType, Integer requestId, Exception error) { - return new BasicCommandAckResponse(requestMsgType, requestId, false, error, null); - } - - private BasicCommandAckResponse(SessionMsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { - super(requestMsgType, requestId, SessionMsgType.TO_DEVICE_RPC_RESPONSE_ACK, success, error, code); - } - - @Override - public String toString() { - return "BasicStatusCodeResponse []"; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java deleted file mode 100644 index e0f6d7ecc4..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java +++ /dev/null @@ -1,59 +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.common.msg.core; - -import lombok.ToString; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -@ToString -public class BasicGetAttributesRequest extends BasicRequest implements GetAttributesRequest { - - private static final long serialVersionUID = 1L; - - private final Set clientKeys; - private final Set sharedKeys; - - public BasicGetAttributesRequest(Integer requestId) { - this(requestId, Collections.emptySet(), Collections.emptySet()); - } - - public BasicGetAttributesRequest(Integer requestId, Set clientKeys, Set sharedKeys) { - super(requestId); - this.clientKeys = clientKeys; - this.sharedKeys = sharedKeys; - } - - @Override - public SessionMsgType getMsgType() { - return SessionMsgType.GET_ATTRIBUTES_REQUEST; - } - - @Override - public Optional> getClientAttributeNames() { - return Optional.ofNullable(clientKeys); - } - - @Override - public Optional> getSharedAttributeNames() { - return Optional.ofNullable(sharedKeys); - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java deleted file mode 100644 index e3eb15d807..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java +++ /dev/null @@ -1,40 +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.common.msg.core; - -import lombok.ToString; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -@ToString -public class BasicGetAttributesResponse extends BasicResponseMsg implements GetAttributesResponse { - - private static final long serialVersionUID = 1L; - - public static BasicGetAttributesResponse onSuccess(SessionMsgType requestMsgType, int requestId, AttributesKVMsg code) { - return new BasicGetAttributesResponse(requestMsgType, requestId, true, null, code); - } - - public static BasicGetAttributesResponse onError(SessionMsgType requestMsgType, int requestId, Exception error) { - return new BasicGetAttributesResponse(requestMsgType, requestId, false, error, null); - } - - private BasicGetAttributesResponse(SessionMsgType requestMsgType, int requestId, boolean success, Exception error, AttributesKVMsg code) { - super(requestMsgType, requestId, SessionMsgType.GET_ATTRIBUTES_RESPONSE, success, error, code); - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java deleted file mode 100644 index c61d8e5e03..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java +++ /dev/null @@ -1,79 +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.common.msg.core; - -import java.io.Serializable; -import java.util.Optional; - -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - - -public class BasicResponseMsg implements ResponseMsg { - - private static final long serialVersionUID = 1L; - - private final SessionMsgType requestMsgType; - private final Integer requestId; - private final SessionMsgType sessionMsgType; - private final boolean success; - private final T data; - private final Exception error; - - protected BasicResponseMsg(SessionMsgType requestMsgType, Integer requestId, SessionMsgType sessionMsgType, boolean success, Exception error, T data) { - super(); - this.requestMsgType = requestMsgType; - this.requestId = requestId; - this.sessionMsgType = sessionMsgType; - this.success = success; - this.error = error; - this.data = data; - } - - @Override - public SessionMsgType getRequestMsgType() { - return requestMsgType; - } - - @Override - public Integer getRequestId() { - return requestId; - } - - @Override - public boolean isSuccess() { - return success; - } - - @Override - public Optional getError() { - return Optional.ofNullable(error); - } - - @Override - public Optional getData() { - return Optional.ofNullable(data); - } - - @Override - public String toString() { - return "BasicResponseMsg [success=" + success + ", data=" + data + ", error=" + error + "]"; - } - - public SessionMsgType getSessionMsgType() { - return sessionMsgType; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java deleted file mode 100644 index f21aa857bf..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java +++ /dev/null @@ -1,42 +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.common.msg.core; - -import lombok.ToString; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -@ToString -public class BasicStatusCodeResponse extends BasicResponseMsg implements StatusCodeResponse { - - private static final long serialVersionUID = 1L; - - public static BasicStatusCodeResponse onSuccess(SessionMsgType requestMsgType, Integer requestId) { - return BasicStatusCodeResponse.onSuccess(requestMsgType, requestId, 0); - } - - public static BasicStatusCodeResponse onSuccess(SessionMsgType requestMsgType, Integer requestId, Integer code) { - return new BasicStatusCodeResponse(requestMsgType, requestId, true, null, code); - } - - public static BasicStatusCodeResponse onError(SessionMsgType requestMsgType, Integer requestId, Exception error) { - return new BasicStatusCodeResponse(requestMsgType, requestId, false, error, null); - } - - private BasicStatusCodeResponse(SessionMsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { - super(requestMsgType, requestId, SessionMsgType.STATUS_CODE_RESPONSE, success, error, code); - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java deleted file mode 100644 index 60faeb1829..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java +++ /dev/null @@ -1,66 +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.common.msg.core; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -public class BasicTelemetryUploadRequest extends BasicRequest implements TelemetryUploadRequest { - - private static final long serialVersionUID = 1L; - - private final Map> data; - - public BasicTelemetryUploadRequest() { - this(DEFAULT_REQUEST_ID); - } - - public BasicTelemetryUploadRequest(Integer requestId) { - super(requestId); - this.data = new HashMap<>(); - } - - public void add(long ts, KvEntry entry) { - List tsEntries = data.get(ts); - if (tsEntries == null) { - tsEntries = new ArrayList<>(); - data.put(ts, tsEntries); - } - tsEntries.add(entry); - } - - @Override - public SessionMsgType getMsgType() { - return SessionMsgType.POST_TELEMETRY_REQUEST; - } - - @Override - public Map> getData() { - return data; - } - - @Override - public String toString() { - return "BasicTelemetryUploadRequest [data=" + data + "]"; - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java deleted file mode 100644 index 241bb6ba1e..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesRequest.java +++ /dev/null @@ -1,29 +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.common.msg.core; - -import java.util.Optional; -import java.util.Set; - -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; - -public interface GetAttributesRequest extends FromDeviceRequestMsg { - - Optional> getClientAttributeNames(); - Optional> getSharedAttributeNames(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesResponse.java deleted file mode 100644 index 6242d46e8f..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/GetAttributesResponse.java +++ /dev/null @@ -1,22 +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.common.msg.core; - -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; - -public interface GetAttributesResponse extends ResponseMsg { - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java deleted file mode 100644 index 3e70460dcb..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java +++ /dev/null @@ -1,33 +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.common.msg.core; - -import java.io.Serializable; -import java.util.Optional; - -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; - -public interface ResponseMsg extends ToDeviceMsg { - - SessionMsgType getRequestMsgType(); - - Integer getRequestId(); - - Optional getError(); - - Optional getData(); -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java deleted file mode 100644 index 23eb238d79..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java +++ /dev/null @@ -1,30 +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.common.msg.core; - -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -/** - * @author Andrew Shvayka - */ -public class RpcUnsubscribeMsg implements FromDeviceMsg { - @Override - public SessionMsgType getMsgType() { - return SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java deleted file mode 100644 index e0ff23b379..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java +++ /dev/null @@ -1,53 +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.common.msg.core; - -import lombok.Data; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; - -/** - * @author Andrew Shvayka - */ -@Data -public class RuleEngineErrorMsg implements ToDeviceMsg { - - private final SessionMsgType inSessionMsgType; - private final RuleEngineError error; - - @Override - public boolean isSuccess() { - return false; - } - - public SessionMsgType getSessionMsgType() { - return SessionMsgType.RULE_ENGINE_ERROR; - } - - public String getErrorMsg() { - switch (error) { - case QUEUE_PUT_TIMEOUT: - return "Timeout during persistence of the message to the queue!"; - case SERVER_ERROR: - return "Error during processing of message by the server!"; - case TIMEOUT: - return "Timeout during processing of message by the server!"; - default: - throw new RuntimeException("Error " + error + " is not supported!"); - } - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java deleted file mode 100644 index bf36a9b3c3..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java +++ /dev/null @@ -1,38 +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.common.msg.core; - -import lombok.ToString; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; - -@ToString -public class SessionCloseNotification implements ToDeviceMsg { - - private static final long serialVersionUID = 1L; - - @Override - public boolean isSuccess() { - return true; - } - - public SessionMsgType getSessionMsgType() { - return SessionMsgType.SESSION_CLOSE; - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java deleted file mode 100644 index 28e319e525..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java +++ /dev/null @@ -1,35 +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.common.msg.core; - -import lombok.Data; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -/** - * @author Andrew Shvayka - */ -@Data -public class SessionOpenMsg implements FromDeviceMsg { - - @Override - public SessionMsgType getMsgType() { - return SessionMsgType.SESSION_OPEN; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java deleted file mode 100644 index b2d8611d1f..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/StatusCodeResponse.java +++ /dev/null @@ -1,20 +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.common.msg.core; - -public interface StatusCodeResponse extends ResponseMsg{ - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java deleted file mode 100644 index 52c2f9e161..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/TelemetryUploadRequest.java +++ /dev/null @@ -1,29 +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.common.msg.core; - -import java.util.List; -import java.util.Map; - -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; - -public interface TelemetryUploadRequest extends FromDeviceRequestMsg { - - Map> getData(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java deleted file mode 100644 index d1a1f96451..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java +++ /dev/null @@ -1,41 +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.common.msg.core; - -import lombok.Data; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; - -/** - * @author Andrew Shvayka - */ -@Data -public class ToDeviceRpcRequestMsg implements ToDeviceMsg { - - private final int requestId; - private final String method; - private final String params; - - public SessionMsgType getSessionMsgType() { - return SessionMsgType.TO_DEVICE_RPC_REQUEST; - } - - @Override - public boolean isSuccess() { - return true; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java deleted file mode 100644 index 4fa3024947..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java +++ /dev/null @@ -1,36 +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.common.msg.core; - -import lombok.Data; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -/** - * @author Andrew Shvayka - */ -@Data -public class ToDeviceRpcResponseMsg implements FromDeviceMsg { - - private final int requestId; - private final String data; - - @Override - public SessionMsgType getMsgType() { - return SessionMsgType.TO_DEVICE_RPC_RESPONSE; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java deleted file mode 100644 index 3823acac70..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java +++ /dev/null @@ -1,36 +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.common.msg.core; - -import lombok.Data; -import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; - -/** - * @author Andrew Shvayka - */ -@Data -public class ToServerRpcRequestMsg implements FromDeviceRequestMsg { - - private final Integer requestId; - private final String method; - private final String params; - - @Override - public SessionMsgType getMsgType() { - return SessionMsgType.TO_SERVER_RPC_REQUEST; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java index 82f44e9113..fa9ef05fe8 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java @@ -16,25 +16,16 @@ package org.thingsboard.server.common.msg.core; import lombok.Data; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; /** * @author Andrew Shvayka */ @Data -public class ToServerRpcResponseMsg implements ToDeviceMsg { +public class ToServerRpcResponseMsg { private final int requestId; private final String data; - public SessionMsgType getSessionMsgType() { - return SessionMsgType.TO_SERVER_RPC_RESPONSE; - } - - @Override public boolean isSuccess() { return true; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicDeviceToDeviceActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicDeviceToDeviceActorMsg.java deleted file mode 100644 index 92c5105673..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicDeviceToDeviceActorMsg.java +++ /dev/null @@ -1,107 +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.common.msg.device; - -import lombok.ToString; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.TransportToDeviceSessionActorMsg; - -import java.util.Optional; - -@ToString -public class BasicDeviceToDeviceActorMsg implements DeviceToDeviceActorMsg { - - private static final long serialVersionUID = -1866795134993115408L; - - private final TenantId tenantId; - private final CustomerId customerId; - private final DeviceId deviceId; - private final SessionId sessionId; - private final SessionType sessionType; - private final ServerAddress serverAddress; - private final FromDeviceMsg msg; - - public BasicDeviceToDeviceActorMsg(DeviceToDeviceActorMsg other, FromDeviceMsg msg) { - this(null, other.getTenantId(), other.getCustomerId(), other.getDeviceId(), other.getSessionId(), other.getSessionType(), msg); - } - - public BasicDeviceToDeviceActorMsg(TransportToDeviceSessionActorMsg msg, SessionType sessionType) { - this(null, msg.getTenantId(), msg.getCustomerId(), msg.getDeviceId(), msg.getSessionId(), sessionType, msg.getSessionMsg().getMsg()); - } - - private BasicDeviceToDeviceActorMsg(ServerAddress serverAddress, TenantId tenantId, CustomerId customerId, DeviceId deviceId, SessionId sessionId, SessionType sessionType, - FromDeviceMsg msg) { - super(); - this.serverAddress = serverAddress; - this.tenantId = tenantId; - this.customerId = customerId; - this.deviceId = deviceId; - this.sessionId = sessionId; - this.sessionType = sessionType; - this.msg = msg; - } - - @Override - public DeviceId getDeviceId() { - return deviceId; - } - - @Override - public CustomerId getCustomerId() { - return customerId; - } - - public TenantId getTenantId() { - return tenantId; - } - - @Override - public SessionId getSessionId() { - return sessionId; - } - - @Override - public SessionType getSessionType() { - return sessionType; - } - - @Override - public Optional getServerAddress() { - return Optional.ofNullable(serverAddress); - } - - @Override - public FromDeviceMsg getPayload() { - return msg; - } - - @Override - public DeviceToDeviceActorMsg toOtherAddress(ServerAddress otherAddress) { - return new BasicDeviceToDeviceActorMsg(otherAddress, tenantId, customerId, deviceId, sessionId, sessionType, msg); - } - - @Override - public MsgType getMsgType() { - return MsgType.DEVICE_SESSION_TO_DEVICE_ACTOR_MSG; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/device/DeviceToDeviceActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/device/DeviceToDeviceActorMsg.java deleted file mode 100644 index fe8d9f2c21..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/device/DeviceToDeviceActorMsg.java +++ /dev/null @@ -1,41 +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.common.msg.device; - -import java.io.Serializable; -import java.util.Optional; - -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.aware.CustomerAwareMsg; -import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; -import org.thingsboard.server.common.msg.aware.TenantAwareMsg; -import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionType; - -public interface DeviceToDeviceActorMsg extends TbActorMsg, DeviceAwareMsg, CustomerAwareMsg, TenantAwareMsg, Serializable { - - SessionId getSessionId(); - - SessionType getSessionType(); - - Optional getServerAddress(); - - FromDeviceMsg getPayload(); - - DeviceToDeviceActorMsg toOtherAddress(ServerAddress otherAddress); -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/AdaptorToSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/AdaptorToSessionActorMsg.java deleted file mode 100644 index 5383a5253e..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/AdaptorToSessionActorMsg.java +++ /dev/null @@ -1,22 +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.common.msg.session; - -public interface AdaptorToSessionActorMsg extends SessionMsg { - - FromDeviceMsg getMsg(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java deleted file mode 100644 index 21c28d25be..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicAdaptorToSessionActorMsg.java +++ /dev/null @@ -1,32 +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.common.msg.session; - -public class BasicAdaptorToSessionActorMsg extends BasicSessionMsg implements AdaptorToSessionActorMsg { - - private final FromDeviceMsg msg; - - public BasicAdaptorToSessionActorMsg(SessionContext ctx, FromDeviceMsg msg) { - super(ctx); - this.msg = msg; - } - - @Override - public FromDeviceMsg getMsg() { - return msg; - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java deleted file mode 100644 index 0e1a087d6e..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionActorToAdaptorMsg.java +++ /dev/null @@ -1,32 +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.common.msg.session; - -public class BasicSessionActorToAdaptorMsg extends BasicSessionMsg implements SessionActorToAdaptorMsg { - - private final ToDeviceMsg msg; - - public BasicSessionActorToAdaptorMsg(SessionContext ctx, ToDeviceMsg msg) { - super(ctx); - this.msg = msg; - } - - @Override - public ToDeviceMsg getMsg() { - return msg; - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java deleted file mode 100644 index 855cce53d7..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicSessionMsg.java +++ /dev/null @@ -1,44 +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.common.msg.session; - -import org.thingsboard.server.common.data.id.SessionId; - -public class BasicSessionMsg implements SessionMsg { - - private final SessionContext ctx; - - public BasicSessionMsg(SessionContext ctx) { - super(); - this.ctx = ctx; - } - - @Override - public SessionId getSessionId() { - return ctx.getSessionId(); - } - - @Override - public SessionContext getSessionContext() { - return ctx; - } - - @Override - public String toString() { - return "BasicSessionMsg [ctx=" + ctx + "]"; - } - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicTransportToDeviceSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicTransportToDeviceSessionActorMsg.java deleted file mode 100644 index 0704e6e1bf..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/BasicTransportToDeviceSessionActorMsg.java +++ /dev/null @@ -1,74 +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.common.msg.session; - -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.MsgType; - -public class BasicTransportToDeviceSessionActorMsg implements TransportToDeviceSessionActorMsg { - - private final TenantId tenantId; - private final CustomerId customerId; - private final DeviceId deviceId; - private final AdaptorToSessionActorMsg msg; - - public BasicTransportToDeviceSessionActorMsg(Device device, AdaptorToSessionActorMsg msg) { - super(); - this.tenantId = device.getTenantId(); - this.customerId = device.getCustomerId(); - this.deviceId = device.getId(); - this.msg = msg; - } - - @Override - public DeviceId getDeviceId() { - return deviceId; - } - - @Override - public CustomerId getCustomerId() { - return customerId; - } - - public TenantId getTenantId() { - return tenantId; - } - - @Override - public SessionId getSessionId() { - return msg.getSessionId(); - } - - @Override - public AdaptorToSessionActorMsg getSessionMsg() { - return msg; - } - - @Override - public String toString() { - return "BasicTransportToDeviceSessionActorMsg [tenantId=" + tenantId + ", customerId=" + customerId + ", deviceId=" + deviceId + ", msg=" + msg - + "]"; - } - - @Override - public MsgType getMsgType() { - return MsgType.TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java deleted file mode 100644 index 71b605712f..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java +++ /dev/null @@ -1,24 +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.common.msg.session; - -import java.io.Serializable; - -public interface FromDeviceMsg extends Serializable { - - SessionMsgType getMsgType(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java deleted file mode 100644 index fa1f6d5e8f..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionActorToAdaptorMsg.java +++ /dev/null @@ -1,22 +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.common.msg.session; - -public interface SessionActorToAdaptorMsg extends SessionMsg { - - ToDeviceMsg getMsg(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java index 2f2d88047e..7f7a669100 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionContext.java @@ -15,21 +15,11 @@ */ package org.thingsboard.server.common.msg.session; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.session.ex.SessionException; +import java.util.UUID; public interface SessionContext { - SessionId getSessionId(); - - SessionType getSessionType(); - - void onMsg(SessionActorToAdaptorMsg msg) throws SessionException; - - void onMsg(SessionCtrlMsg msg) throws SessionException; - - boolean isClosed(); - - long getTimeout(); + UUID getSessionId(); + int nextMsgId(); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java deleted file mode 100644 index 8082f72184..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionCtrlMsg.java +++ /dev/null @@ -1,23 +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.common.msg.session; - -import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; - -public interface SessionCtrlMsg extends SessionAwareMsg, TbActorMsg { - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java deleted file mode 100644 index 6b0b0af15d..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsg.java +++ /dev/null @@ -1,24 +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.common.msg.session; - -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; - -public interface SessionMsg extends SessionAwareMsg { - - SessionContext getSessionContext(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionType.java deleted file mode 100644 index 831682d165..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionType.java +++ /dev/null @@ -1,22 +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.common.msg.session; - -public enum SessionType { - - SYNC, ASYNC; - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java deleted file mode 100644 index 705e86420b..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java +++ /dev/null @@ -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.common.msg.session; - -import java.io.Serializable; - -public interface ToDeviceMsg extends Serializable { - - boolean isSuccess(); - - SessionMsgType getSessionMsgType(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/TransportToDeviceSessionActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/TransportToDeviceSessionActorMsg.java deleted file mode 100644 index deaef3d3e5..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/TransportToDeviceSessionActorMsg.java +++ /dev/null @@ -1,28 +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.common.msg.session; - -import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.aware.CustomerAwareMsg; -import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; -import org.thingsboard.server.common.msg.aware.TenantAwareMsg; - -public interface TransportToDeviceSessionActorMsg extends DeviceAwareMsg, CustomerAwareMsg, TenantAwareMsg, SessionAwareMsg, TbActorMsg { - - AdaptorToSessionActorMsg getSessionMsg(); - -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java deleted file mode 100644 index 49fad13236..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ctrl/SessionCloseMsg.java +++ /dev/null @@ -1,68 +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.common.msg.session.ctrl; - -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.session.SessionCtrlMsg; - -public class SessionCloseMsg implements SessionCtrlMsg { - - private final SessionId sessionId; - private final boolean revoked; - private final boolean timeout; - - public static SessionCloseMsg onDisconnect(SessionId sessionId) { - return new SessionCloseMsg(sessionId, false, false); - } - - public static SessionCloseMsg onError(SessionId sessionId) { - return new SessionCloseMsg(sessionId, false, false); - } - - public static SessionCloseMsg onTimeout(SessionId sessionId) { - return new SessionCloseMsg(sessionId, false, true); - } - - public static SessionCloseMsg onCredentialsRevoked(SessionId sessionId) { - return new SessionCloseMsg(sessionId, true, false); - } - - private SessionCloseMsg(SessionId sessionId, boolean unauthorized, boolean timeout) { - super(); - this.sessionId = sessionId; - this.revoked = unauthorized; - this.timeout = timeout; - } - - @Override - public SessionId getSessionId() { - return sessionId; - } - - public boolean isCredentialsRevoked() { - return revoked; - } - - public boolean isTimeout() { - return timeout; - } - - @Override - public MsgType getMsgType() { - return MsgType.SESSION_CTRL_MSG; - } -} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorQueueTimeoutMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorQueueTimeoutMsg.java deleted file mode 100644 index 0277983d8b..0000000000 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorQueueTimeoutMsg.java +++ /dev/null @@ -1,36 +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.common.msg.timeout; - -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.timeout.TimeoutMsg; - -import java.util.UUID; - -/** - * @author Andrew Shvayka - */ -public final class DeviceActorQueueTimeoutMsg extends TimeoutMsg { - - public DeviceActorQueueTimeoutMsg(UUID id, long timeout) { - super(id, timeout); - } - - @Override - public MsgType getMsgType() { - return MsgType.DEVICE_ACTOR_QUEUE_TIMEOUT_MSG; - } -} diff --git a/common/pom.xml b/common/pom.xml index e3cdabff98..e55d71fcd3 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -23,7 +23,6 @@ 2.2.0-SNAPSHOT thingsboard - org.thingsboard common pom @@ -37,8 +36,8 @@ data message - transport queue + transport diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 11bb13981f..765960e6a7 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -16,7 +16,7 @@ --> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.thingsboard @@ -64,6 +64,10 @@ com.google.code.gson gson + + org.apache.commons + commons-lang3 + org.slf4j slf4j-api diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/AbstractTbKafkaTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/AbstractTbKafkaTemplate.java new file mode 100644 index 0000000000..386bfc574e --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/AbstractTbKafkaTemplate.java @@ -0,0 +1,50 @@ +/** + * 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.kafka; + +import lombok.extern.slf4j.Slf4j; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +/** + * Created by ashvayka on 25.09.18. + */ +@Slf4j +public abstract class AbstractTbKafkaTemplate { + protected byte[] uuidToBytes(UUID uuid) { + ByteBuffer buf = ByteBuffer.allocate(16); + buf.putLong(uuid.getMostSignificantBits()); + buf.putLong(uuid.getLeastSignificantBits()); + return buf.array(); + } + + protected static UUID bytesToUuid(byte[] bytes) { + ByteBuffer bb = ByteBuffer.wrap(bytes); + long firstLong = bb.getLong(); + long secondLong = bb.getLong(); + return new UUID(firstLong, secondLong); + } + + protected byte[] stringToBytes(String string) { + return string.getBytes(StandardCharsets.UTF_8); + } + + protected String bytesToString(byte[] data) { + return new String(data, StandardCharsets.UTF_8); + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java new file mode 100644 index 0000000000..b8ad758ef9 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java @@ -0,0 +1,66 @@ +/** + * 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.kafka; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Created by ashvayka on 05.10.18. + */ +public class AsyncCallbackTemplate { + + public static void withCallbackAndTimeout(ListenableFuture future, + Consumer onSuccess, + Consumer onFailure, + long timeoutInMs, + ScheduledExecutorService timeoutExecutor, + Executor callbackExecutor) { + future = Futures.withTimeout(future, timeoutInMs, TimeUnit.MILLISECONDS, timeoutExecutor); + withCallback(future, onSuccess, onFailure, callbackExecutor); + } + + public static void withCallback(ListenableFuture future, Consumer onSuccess, + Consumer onFailure, Executor executor) { + FutureCallback callback = new FutureCallback() { + @Override + public void onSuccess(T result) { + try { + onSuccess.accept(result); + } catch (Throwable th) { + onFailure(th); + } + } + + @Override + public void onFailure(Throwable t) { + onFailure.accept(t); + } + }; + if (executor != null) { + Futures.addCallback(future, callback, executor); + } else { + Futures.addCallback(future, callback); + } + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java index 397226479e..86be3a3c38 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java @@ -43,12 +43,15 @@ public class TBKafkaConsumerTemplate { private final String topic; @Builder - private TBKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder decoder, TbKafkaRequestIdExtractor requestIdExtractor, + private TBKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder decoder, + TbKafkaRequestIdExtractor requestIdExtractor, String clientId, String groupId, String topic, boolean autoCommit, int autoCommitIntervalMs) { Properties props = settings.toProps(); props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId); - props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); + if (groupId != null) { + props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); + } props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, autoCommit); props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java index 7e24ad00a6..610a49007d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java @@ -20,6 +20,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.CreateTopicsResult; import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.producer.Callback; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; @@ -27,13 +28,12 @@ import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.common.PartitionInfo; import org.apache.kafka.common.header.Header; -import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Future; -import java.util.function.BiConsumer; /** * Created by ashvayka on 24.09.18. @@ -48,7 +48,7 @@ public class TBKafkaProducerTemplate { private TbKafkaEnricher enricher = ((value, responseTopic, requestId) -> value); private final TbKafkaPartitioner partitioner; - private List partitionInfoList; + private ConcurrentMap> partitionInfoMap; @Getter private final String defaultTopic; @@ -78,38 +78,51 @@ public class TBKafkaProducerTemplate { log.trace("Failed to create topic: {}", e.getMessage(), e); } //Maybe this should not be cached, but we don't plan to change size of partitions - this.partitionInfoList = producer.partitionsFor(defaultTopic); + this.partitionInfoMap = new ConcurrentHashMap<>(); + this.partitionInfoMap.putIfAbsent(defaultTopic, producer.partitionsFor(defaultTopic)); } - public T enrich(T value, String responseTopic, UUID requestId) { - return enricher.enrich(value, responseTopic, requestId); + T enrich(T value, String responseTopic, UUID requestId) { + if (enricher != null) { + return enricher.enrich(value, responseTopic, requestId); + } else { + return value; + } + } + + public Future send(String key, T value, Callback callback) { + return send(key, value, null, callback); + } + + public Future send(String key, T value, Iterable
headers, Callback callback) { + return send(key, value, null, headers, callback); } - public Future send(String key, T value) { - return send(key, value, null, null); + public Future send(String key, T value, Long timestamp, Iterable
headers, Callback callback) { + return send(this.defaultTopic, key, value, timestamp, headers, callback); } - public Future send(String key, T value, Iterable
headers) { - return send(key, value, null, headers); + public Future send(String topic, String key, T value, Iterable
headers, Callback callback) { + return send(topic, key, value, null, headers, callback); } - public Future send(String key, T value, Long timestamp, Iterable
headers) { - return send(this.defaultTopic, key, value, timestamp, headers); + public Future send(String topic, String key, T value, Callback callback) { + return send(topic, key, value, null, null, callback); } - public Future send(String topic, String key, T value, Long timestamp, Iterable
headers) { + public Future send(String topic, String key, T value, Long timestamp, Iterable
headers, Callback callback) { byte[] data = encoder.encode(value); ProducerRecord record; Integer partition = getPartition(topic, key, value, data); - record = new ProducerRecord<>(this.defaultTopic, partition, timestamp, key, data, headers); - return producer.send(record); + record = new ProducerRecord<>(topic, partition, timestamp, key, data, headers); + return producer.send(record, callback); } private Integer getPartition(String topic, String key, T value, byte[] data) { if (partitioner == null) { return null; } else { - return partitioner.partition(this.defaultTopic, key, value, data, partitionInfoList); + return partitioner.partition(topic, key, value, data, partitionInfoMap.computeIfAbsent(topic, producer::partitionsFor)); } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgKey.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaHandler.java similarity index 72% rename from dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgKey.java rename to common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaHandler.java index bb44a2b192..9f86155611 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgKey.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaHandler.java @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.queue.memory; +package org.thingsboard.server.kafka; -import lombok.Data; - -import java.util.UUID; +import com.google.common.util.concurrent.ListenableFuture; /** - * Created by ashvayka on 30.04.18. + * Created by ashvayka on 05.10.18. */ -@Data -public final class InMemoryMsgKey { - final UUID nodeId; - final long clusterPartition; +public interface TbKafkaHandler { + + ListenableFuture handle(Request request); + } diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java index 8a0f5293d9..77ad0331ab 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java @@ -23,24 +23,25 @@ import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.CreateTopicsResult; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.common.header.Header; import org.apache.kafka.common.header.internals.RecordHeader; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeoutException; /** * Created by ashvayka on 25.09.18. */ @Slf4j -public class TbKafkaRequestTemplate { +public class TbKafkaRequestTemplate extends AbstractTbKafkaTemplate { private final TBKafkaProducerTemplate requestTemplate; private final TBKafkaConsumerTemplate responseTemplate; @@ -93,13 +94,12 @@ public class TbKafkaRequestTemplate { ConsumerRecords responses = responseTemplate.poll(Duration.ofMillis(pollInterval)); responses.forEach(response -> { Header requestIdHeader = response.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER); - Response decocedResponse = null; + Response decodedResponse = null; UUID requestId = null; if (requestIdHeader == null) { try { - decocedResponse = responseTemplate.decode(response); - requestId = responseTemplate.extractRequestId(decocedResponse); - + decodedResponse = responseTemplate.decode(response); + requestId = responseTemplate.extractRequestId(decodedResponse); } catch (IOException e) { log.error("Failed to decode response", e); } @@ -107,17 +107,17 @@ public class TbKafkaRequestTemplate { requestId = bytesToUuid(requestIdHeader.value()); } if (requestId == null) { - log.error("[{}] Missing requestId in header and response", response); + log.error("[{}] Missing requestId in header and body", response); } else { ResponseMetaData expectedResponse = pendingRequests.remove(requestId); if (expectedResponse == null) { log.trace("[{}] Invalid or stale request", requestId); } else { try { - if (decocedResponse == null) { - decocedResponse = responseTemplate.decode(response); + if (decodedResponse == null) { + decodedResponse = responseTemplate.decode(response); } - expectedResponse.future.set(decocedResponse); + expectedResponse.future.set(decodedResponse); } catch (IOException e) { expectedResponse.future.setException(e); } @@ -160,28 +160,10 @@ public class TbKafkaRequestTemplate { SettableFuture future = SettableFuture.create(); pendingRequests.putIfAbsent(requestId, new ResponseMetaData<>(tickTs + maxRequestTimeout, future)); request = requestTemplate.enrich(request, responseTemplate.getTopic(), requestId); - requestTemplate.send(key, request, headers); + requestTemplate.send(key, request, headers, null); return future; } - private byte[] uuidToBytes(UUID uuid) { - ByteBuffer buf = ByteBuffer.allocate(16); - buf.putLong(uuid.getMostSignificantBits()); - buf.putLong(uuid.getLeastSignificantBits()); - return buf.array(); - } - - private static UUID bytesToUuid(byte[] bytes) { - ByteBuffer bb = ByteBuffer.wrap(bytes); - long firstLong = bb.getLong(); - long secondLong = bb.getLong(); - return new UUID(firstLong, secondLong); - } - - private byte[] stringToBytes(String string) { - return string.getBytes(StandardCharsets.UTF_8); - } - private static class ResponseMetaData { private final long expTime; private final SettableFuture future; diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java new file mode 100644 index 0000000000..4c23ac2211 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java @@ -0,0 +1,156 @@ +/** + * 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.kafka; + +import lombok.Builder; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.common.header.Header; +import org.apache.kafka.common.header.internals.RecordHeader; + +import java.time.Duration; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by ashvayka on 25.09.18. + */ +@Slf4j +public class TbKafkaResponseTemplate extends AbstractTbKafkaTemplate { + + private final TBKafkaConsumerTemplate requestTemplate; + private final TBKafkaProducerTemplate responseTemplate; + private final TbKafkaHandler handler; + private final ConcurrentMap pendingRequests; + private final ExecutorService loopExecutor; + private final ScheduledExecutorService timeoutExecutor; + private final ExecutorService callbackExecutor; + private final int maxPendingRequests; + private final long requestTimeout; + + private final long pollInterval; + private volatile boolean stopped = false; + private final AtomicInteger pendingRequestCount = new AtomicInteger(); + + @Builder + public TbKafkaResponseTemplate(TBKafkaConsumerTemplate requestTemplate, + TBKafkaProducerTemplate responseTemplate, + TbKafkaHandler handler, + long pollInterval, + long requestTimeout, + int maxPendingRequests, + ExecutorService executor) { + this.requestTemplate = requestTemplate; + this.responseTemplate = responseTemplate; + this.handler = handler; + this.pendingRequests = new ConcurrentHashMap<>(); + this.maxPendingRequests = maxPendingRequests; + this.pollInterval = pollInterval; + this.requestTimeout = requestTimeout; + this.callbackExecutor = executor; + this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(); + this.loopExecutor = Executors.newSingleThreadExecutor(); + } + + public void init() { + this.responseTemplate.init(); + requestTemplate.subscribe(); + loopExecutor.submit(() -> { + while (!stopped) { + try { + while (pendingRequestCount.get() >= maxPendingRequests) { + try { + Thread.sleep(pollInterval); + } catch (InterruptedException e) { + log.trace("Failed to wait until the server has capacity to handle new requests", e); + } + } + ConsumerRecords requests = requestTemplate.poll(Duration.ofMillis(pollInterval)); + requests.forEach(request -> { + Header requestIdHeader = request.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER); + if (requestIdHeader == null) { + log.error("[{}] Missing requestId in header", request); + return; + } + UUID requestId = bytesToUuid(requestIdHeader.value()); + if (requestId == null) { + log.error("[{}] Missing requestId in header and body", request); + return; + } + Header responseTopicHeader = request.headers().lastHeader(TbKafkaSettings.RESPONSE_TOPIC_HEADER); + if (responseTopicHeader == null) { + log.error("[{}] Missing response topic in header", request); + return; + } + String responseTopic = bytesToString(responseTopicHeader.value()); + try { + pendingRequestCount.getAndIncrement(); + Request decodedRequest = requestTemplate.decode(request); + AsyncCallbackTemplate.withCallbackAndTimeout(handler.handle(decodedRequest), + response -> { + pendingRequestCount.decrementAndGet(); + reply(requestId, responseTopic, response); + }, + e -> { + pendingRequestCount.decrementAndGet(); + if (e.getCause() != null && e.getCause() instanceof TimeoutException) { + log.warn("[{}] Timedout to process the request: {}", requestId, request, e); + } else { + log.trace("[{}] Failed to process the request: {}", requestId, request, e); + } + }, + requestTimeout, + timeoutExecutor, + callbackExecutor); + } catch (Throwable e) { + pendingRequestCount.decrementAndGet(); + log.warn("[{}] Failed to process the request: {}", requestId, request, e); + } + }); + } catch (Throwable e) { + log.warn("Failed to obtain messages from queue.", e); + try { + Thread.sleep(pollInterval); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new requests", e2); + } + } + } + }); + } + + public void stop() { + stopped = true; + if (timeoutExecutor != null) { + timeoutExecutor.shutdownNow(); + } + if (loopExecutor != null) { + loopExecutor.shutdownNow(); + } + } + + private void reply(UUID requestId, String topic, Response response) { + responseTemplate.send(topic, requestId.toString(), response, Collections.singletonList(new RecordHeader(TbKafkaSettings.REQUEST_ID_HEADER, uuidToBytes(requestId))), null); + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbNodeIdProvider.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbNodeIdProvider.java new file mode 100644 index 0000000000..47dd725682 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbNodeIdProvider.java @@ -0,0 +1,51 @@ +/** + * 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.kafka; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import javax.annotation.PostConstruct; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Created by ashvayka on 12.10.18. + */ +@Slf4j +@Component +public class TbNodeIdProvider { + + @Getter + @Value("${cluster.node_id:#{null}}") + private String nodeId; + + @PostConstruct + public void init() { + if (StringUtils.isEmpty(nodeId)) { + try { + nodeId = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + nodeId = org.apache.commons.lang3.RandomStringUtils.randomAlphabetic(10); + } + } + log.info("Current NodeId: {}", nodeId); + } + +} diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml new file mode 100644 index 0000000000..d68a6db39f --- /dev/null +++ b/common/transport/coap/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + org.thingsboard.common + 2.2.0-SNAPSHOT + transport + + org.thingsboard.common.transport + coap + jar + + Thingsboard CoAP Transport Common + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common.transport + transport-api + + + org.eclipse.californium + californium-core + + + org.springframework + spring-context-support + + + org.springframework + spring-context + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java new file mode 100644 index 0000000000..13662cf1be --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportContext.java @@ -0,0 +1,51 @@ +/** + * 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.transport.coap; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; + +/** + * Created by ashvayka on 18.10.18. + */ +@Slf4j +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')") +@Component +public class CoapTransportContext extends TransportContext { + + @Getter + @Value("${transport.coap.bind_address}") + private String host; + + @Getter + @Value("${transport.coap.bind_port}") + private Integer port; + + @Getter + @Value("${transport.coap.timeout}") + private Long timeout; + + @Getter + @Autowired + private CoapTransportAdaptor adaptor; + +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java new file mode 100644 index 0000000000..0e2a794a45 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -0,0 +1,445 @@ +/** + * 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.transport.coap; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapResource; +import org.eclipse.californium.core.coap.CoAP.ResponseCode; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.network.ExchangeObserver; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.springframework.util.ReflectionUtils; +import org.thingsboard.server.common.data.security.DeviceTokenCredentials; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +@Slf4j +public class CoapTransportResource extends CoapResource { + // coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]] + private static final int ACCESS_TOKEN_POSITION = 3; + private static final int FEATURE_TYPE_POSITION = 4; + private static final int REQUEST_ID_POSITION = 5; + + private final CoapTransportContext transportContext; + private final TransportService transportService; + private final Field observerField; + private final long timeout; + private final ConcurrentMap tokenToSessionIdMap = new ConcurrentHashMap<>(); + + public CoapTransportResource(CoapTransportContext context, String name) { + super(name); + this.transportContext = context; + this.transportService = context.getTransportService(); + this.timeout = context.getTimeout(); + // This is important to turn off existing observable logic in + // CoapResource. We will have our own observe monitoring due to 1:1 + // observe relationship. + this.setObservable(false); + observerField = ReflectionUtils.findField(Exchange.class, "observer"); + observerField.setAccessible(true); + } + + @Override + public void handleGET(CoapExchange exchange) { + if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { + log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); + exchange.respond(ResponseCode.BAD_REQUEST); + return; + } + + Optional featureType = getFeatureType(exchange.advanced().getRequest()); + if (!featureType.isPresent()) { + log.trace("Missing feature type parameter"); + exchange.respond(ResponseCode.BAD_REQUEST); + } else if (featureType.get() == FeatureType.TELEMETRY) { + log.trace("Can't fetch/subscribe to timeseries updates"); + exchange.respond(ResponseCode.BAD_REQUEST); + } else if (exchange.getRequestOptions().hasObserve()) { + processExchangeGetRequest(exchange, featureType.get()); + } else if (featureType.get() == FeatureType.ATTRIBUTES) { + processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); + } else { + log.trace("Invalid feature type parameter"); + exchange.respond(ResponseCode.BAD_REQUEST); + } + } + + private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { + boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; + SessionMsgType sessionMsgType; + if (featureType == FeatureType.RPC) { + sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + } else { + sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + } + processRequest(exchange, sessionMsgType); + } + + @Override + public void handlePOST(CoapExchange exchange) { + if (transportContext.getQuotaService().isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { + log.warn("CoAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); + exchange.respond(ResponseCode.BAD_REQUEST); + return; + } + + Optional featureType = getFeatureType(exchange.advanced().getRequest()); + if (!featureType.isPresent()) { + log.trace("Missing feature type parameter"); + exchange.respond(ResponseCode.BAD_REQUEST); + } else { + switch (featureType.get()) { + case ATTRIBUTES: + processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); + break; + case TELEMETRY: + processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); + break; + case RPC: + Optional requestId = getRequestId(exchange.advanced().getRequest()); + if (requestId.isPresent()) { + processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); + } else { + processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); + } + break; + } + } + } + + private void processRequest(CoapExchange exchange, SessionMsgType type) { + log.trace("Processing {}", exchange.advanced().getRequest()); + exchange.accept(); + Exchange advanced = exchange.advanced(); + Request request = advanced.getRequest(); + + Optional credentials = decodeCredentials(request); + if (!credentials.isPresent()) { + exchange.respond(ResponseCode.BAD_REQUEST); + return; + } + + transportService.process(TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), + new DeviceAuthCallback(transportContext, exchange, sessionInfo -> { + UUID sessionId = new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); + try { + switch (type) { + case POST_ATTRIBUTES_REQUEST: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToPostAttributes(sessionId, request), + new CoapOkCallback(exchange)); + break; + case POST_TELEMETRY_REQUEST: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToPostTelemetry(sessionId, request), + new CoapOkCallback(exchange)); + break; + case SUBSCRIBE_ATTRIBUTES_REQUEST: + advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), + registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); + transportService.process(sessionInfo, + TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), + new CoapNoOpCallback(exchange)); + break; + case UNSUBSCRIBE_ATTRIBUTES_REQUEST: + TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(request); + if (attrSession != null) { + transportService.process(attrSession, + TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), + new CoapOkCallback(exchange)); + closeAndDeregister(sessionInfo); + } + break; + case SUBSCRIBE_RPC_COMMANDS_REQUEST: + advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), + registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); + transportService.process(sessionInfo, + TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), + new CoapNoOpCallback(exchange)); + break; + case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: + TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(request); + if (rpcSession != null) { + transportService.process(rpcSession, + TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), + new CoapOkCallback(exchange)); + transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); + transportService.deregisterSession(rpcSession); + } + break; + case TO_DEVICE_RPC_RESPONSE: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToDeviceRpcResponse(sessionId, request), + new CoapOkCallback(exchange)); + break; + case TO_SERVER_RPC_REQUEST: + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToServerRpcRequest(sessionId, request), + new CoapNoOpCallback(exchange)); + break; + case GET_ATTRIBUTES_REQUEST: + transportService.registerSyncSession(sessionInfo, new CoapSessionListener(sessionId, exchange), transportContext.getTimeout()); + transportService.process(sessionInfo, + transportContext.getAdaptor().convertToGetAttributes(sessionId, request), + new CoapNoOpCallback(exchange)); + break; + } + } catch (AdaptorException e) { + log.trace("[{}] Failed to decode message: ", sessionId, e); + exchange.respond(ResponseCode.BAD_REQUEST); + } catch (IllegalAccessException e) { + log.trace("[{}] Failed to process message: ", sessionId, e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + })); + } + + private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) { + String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); + return tokenToSessionIdMap.remove(token); + } + + private String registerAsyncCoapSession(CoapExchange exchange, Request request, TransportProtos.SessionInfoProto sessionInfo, UUID sessionId) { + String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); + tokenToSessionIdMap.putIfAbsent(token, sessionInfo); + CoapSessionListener attrListener = new CoapSessionListener(sessionId, exchange); + transportService.registerAsyncSession(sessionInfo, attrListener); + transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); + return token; + } + + private static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { + return TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(event).build(); + } + + private Optional decodeCredentials(Request request) { + List uriPath = request.getOptions().getUriPath(); + if (uriPath.size() >= ACCESS_TOKEN_POSITION) { + return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); + } else { + return Optional.empty(); + } + } + + private Optional getFeatureType(Request request) { + List uriPath = request.getOptions().getUriPath(); + try { + if (uriPath.size() >= FEATURE_TYPE_POSITION) { + return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); + } + } catch (RuntimeException e) { + log.warn("Failed to decode feature type: {}", uriPath); + } + return Optional.empty(); + } + + public static Optional getRequestId(Request request) { + List uriPath = request.getOptions().getUriPath(); + try { + if (uriPath.size() >= REQUEST_ID_POSITION) { + return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); + } + } catch (RuntimeException e) { + log.warn("Failed to decode feature type: {}", uriPath); + } + return Optional.empty(); + } + + @Override + public Resource getChild(String name) { + return this; + } + + private static class DeviceAuthCallback implements TransportServiceCallback { + private final TransportContext transportContext; + private final CoapExchange exchange; + private final Consumer onSuccess; + + DeviceAuthCallback(TransportContext transportContext, CoapExchange exchange, Consumer onSuccess) { + this.transportContext = transportContext; + this.exchange = exchange; + this.onSuccess = onSuccess; + } + + @Override + public void onSuccess(TransportProtos.ValidateDeviceCredentialsResponseMsg msg) { + if (msg.hasDeviceInfo()) { + UUID sessionId = UUID.randomUUID(); + TransportProtos.DeviceInfoProto deviceInfoProto = msg.getDeviceInfo(); + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setNodeId(transportContext.getNodeId()) + .setTenantIdMSB(deviceInfoProto.getTenantIdMSB()) + .setTenantIdLSB(deviceInfoProto.getTenantIdLSB()) + .setDeviceIdMSB(deviceInfoProto.getDeviceIdMSB()) + .setDeviceIdLSB(deviceInfoProto.getDeviceIdLSB()) + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .build(); + onSuccess.accept(sessionInfo); + } else { + exchange.respond(ResponseCode.UNAUTHORIZED); + } + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + private static class CoapOkCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + CoapOkCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(Void msg) { + exchange.respond(ResponseCode.VALID); + } + + @Override + public void onError(Throwable e) { + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + private static class CoapNoOpCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + CoapNoOpCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(Void msg) { + + } + + @Override + public void onError(Throwable e) { + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + public class CoapSessionListener implements SessionMsgListener { + + private final CoapExchange exchange; + private final AtomicInteger seqNumber = new AtomicInteger(2); + + CoapSessionListener(UUID sessionId, CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { + exchange.respond(ResponseCode.SERVICE_UNAVAILABLE); + } + + @Override + public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + @Override + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg msg) { + try { + exchange.respond(transportContext.getAdaptor().convertToPublish(this, msg)); + } catch (AdaptorException e) { + log.trace("Failed to reply due to error", e); + exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + public int getNextSeqNumber() { + return seqNumber.getAndIncrement(); + } + } + + public class CoapExchangeObserverProxy implements ExchangeObserver { + + private final ExchangeObserver proxy; + private final String token; + + CoapExchangeObserverProxy(ExchangeObserver proxy, String token) { + super(); + this.proxy = proxy; + this.token = token; + } + + @Override + public void completed(Exchange exchange) { + proxy.completed(exchange); + TransportProtos.SessionInfoProto session = tokenToSessionIdMap.remove(token); + if (session != null) { + closeAndDeregister(session); + } + } + } + + private void closeAndDeregister(TransportProtos.SessionInfoProto session) { + transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); + transportService.deregisterSession(session); + } + +} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java similarity index 72% rename from transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java rename to common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index 6c8437cb03..278ca6d8c5 100644 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -21,6 +21,7 @@ import org.eclipse.californium.core.CoapServer; import org.eclipse.californium.core.network.CoapEndpoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; @@ -37,49 +38,26 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; @Service("CoapTransportService") -@ConditionalOnProperty(prefix = "coap", value = "enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.coap.enabled}'=='true')") @Slf4j public class CoapTransportService { private static final String V1 = "v1"; private static final String API = "api"; - private CoapServer server; - - @Autowired(required = false) - private ApplicationContext appContext; - - @Autowired(required = false) - private SessionMsgProcessor processor; - - @Autowired(required = false) - private DeviceAuthService authService; + @Autowired + private CoapTransportContext coapTransportContext; - @Autowired(required = false) - private HostRequestsQuotaService quotaService; - - - @Value("${coap.bind_address}") - private String host; - @Value("${coap.bind_port}") - private Integer port; - @Value("${coap.adaptor}") - private String adaptorName; - @Value("${coap.timeout}") - private Long timeout; - - private CoapTransportAdaptor adaptor; + private CoapServer server; @PostConstruct public void init() throws UnknownHostException { log.info("Starting CoAP transport..."); - log.info("Lookup CoAP transport adaptor {}", adaptorName); - this.adaptor = (CoapTransportAdaptor) appContext.getBean(adaptorName); log.info("Starting CoAP transport server"); this.server = new CoapServer(); createResources(); - InetAddress addr = InetAddress.getByName(host); - InetSocketAddress sockAddr = new InetSocketAddress(addr, port); + InetAddress addr = InetAddress.getByName(coapTransportContext.getHost()); + InetSocketAddress sockAddr = new InetSocketAddress(addr, coapTransportContext.getPort()); server.addEndpoint(new CoapEndpoint(sockAddr)); server.start(); log.info("CoAP transport started!"); @@ -87,7 +65,7 @@ public class CoapTransportService { private void createResources() { CoapResource api = new CoapResource(API); - api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService)); + api.add(new CoapTransportResource(coapTransportContext, V1)); server.add(api); } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java new file mode 100644 index 0000000000..9e48cd3030 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java @@ -0,0 +1,47 @@ +/** + * 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.transport.coap.adaptors; + +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.CoapTransportResource; + +import java.util.UUID; +import java.util.Optional; + +public interface CoapTransportAdaptor { + + TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException; + + TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; + + Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; + +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java new file mode 100644 index 0000000000..f5bfb49c08 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -0,0 +1,155 @@ +/** + * 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.transport.coap.adaptors; + +import java.util.*; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.msg.kv.AttributesKVMsg; +import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.CoapTransportResource; + +@Component("JsonCoapAdaptor") +@Slf4j +public class JsonCoapAdaptor implements CoapTransportAdaptor { + + @Override + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(UUID sessionId, Request inbound) throws AdaptorException { + String payload = validatePayload(sessionId, inbound); + try { + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.PostAttributeMsg convertToPostAttributes(UUID sessionId, Request inbound) throws AdaptorException { + String payload = validatePayload(sessionId, inbound); + try { + return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException { + List queryElements = inbound.getOptions().getUriQuery(); + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); + if (queryElements != null && queryElements.size() > 0) { + Set clientKeys = toKeys(queryElements, "clientKeys"); + Set sharedKeys = toKeys(queryElements, "sharedKeys"); + if (clientKeys != null) { + result.addAllClientAttributeNames(clientKeys); + } + if (sharedKeys != null) { + result.addAllSharedAttributeNames(sharedKeys); + } + } + return result.build(); + } + + @Override + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException { + Optional requestId = CoapTransportResource.getRequestId(inbound); + String payload = validatePayload(sessionId, inbound); + JsonObject response = new JsonParser().parse(payload).getAsJsonObject(); + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!"))) + .setPayload(response.toString()).build(); + } + + @Override + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException { + String payload = validatePayload(sessionId, inbound); + return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0); + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.AttributeUpdateNotificationMsg msg) throws AdaptorException { + return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg)); + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { + return getObserveNotification(session.getNextSeqNumber(), JsonConverter.toJson(msg, true)); + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener coapSessionListener, TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException { + Response response = new Response(CoAP.ResponseCode.CONTENT); + JsonElement result = JsonConverter.toJson(msg); + response.setPayload(result.toString()); + return response; + } + + @Override + public Response convertToPublish(CoapTransportResource.CoapSessionListener session, TransportProtos.GetAttributeResponseMsg msg) throws AdaptorException { + if (msg.getClientAttributeListCount() == 0 && msg.getSharedAttributeListCount() == 0 && msg.getDeletedAttributeKeysCount() == 0) { + return new Response(CoAP.ResponseCode.NOT_FOUND); + } else { + Response response = new Response(CoAP.ResponseCode.CONTENT); + JsonObject result = JsonConverter.toJson(msg); + response.setPayload(result.toString()); + return response; + } + } + + private Response getObserveNotification(int seqNumber, JsonElement json) { + Response response = new Response(CoAP.ResponseCode.CONTENT); + response.getOptions().setObserve(seqNumber); + response.setPayload(json.toString()); + return response; + } + + private String validatePayload(UUID sessionId, Request inbound) throws AdaptorException { + String payload = inbound.getPayloadString(); + if (payload == null) { + log.warn("[{}] Payload is empty!", sessionId); + throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); + } + return payload; + } + + private Set toKeys(List queryElements, String attributeName) throws AdaptorException { + String keys = null; + for (String queryElement : queryElements) { + String[] queryItem = queryElement.split("="); + if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { + keys = queryItem[1]; + } + } + if (keys != null && !StringUtils.isEmpty(keys)) { + return new HashSet<>(Arrays.asList(keys.split(","))); + } else { + return null; + } + } + +} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java similarity index 100% rename from transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java rename to common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DeviceEmulator.java diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml new file mode 100644 index 0000000000..5d735c9def --- /dev/null +++ b/common/transport/http/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + org.thingsboard.common + 2.2.0-SNAPSHOT + transport + + org.thingsboard.common.transport + http + jar + + Thingsboard HTTP Transport Common + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common.transport + transport-api + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java new file mode 100644 index 0000000000..b40fbc194f --- /dev/null +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -0,0 +1,326 @@ +/** + * 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.transport.http; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +/** + * @author Andrew Shvayka + */ +@RestController +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')") +@RequestMapping("/api/v1") +@Slf4j +public class DeviceApiController { + + @Autowired + private HttpTransportContext transportContext; + + @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") + public DeferredResult getDeviceAttributes(@PathVariable("deviceToken") String deviceToken, + @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, + @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys, + HttpServletRequest httpRequest) { + DeferredResult responseWriter = new DeferredResult<>(); + if (quotaExceeded(httpRequest, responseWriter)) { + return responseWriter; + } + transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + GetAttributeRequestMsg.Builder request = GetAttributeRequestMsg.newBuilder().setRequestId(0); + List clientKeySet = !StringUtils.isEmpty(clientKeys) ? Arrays.asList(clientKeys.split(",")) : null; + List sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? Arrays.asList(sharedKeys.split(",")) : null; + if (clientKeySet != null) { + request.addAllClientAttributeNames(clientKeySet); + } + if (sharedKeySet != null) { + request.addAllSharedAttributeNames(sharedKeySet); + } + TransportService transportService = transportContext.getTransportService(); + transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout()); + transportService.process(sessionInfo, request.build(), new SessionCloseOnErrorCallback(transportService, sessionInfo)); + })); + return responseWriter; + } + + @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST) + public DeferredResult postDeviceAttributes(@PathVariable("deviceToken") String deviceToken, + @RequestBody String json, HttpServletRequest request) { + DeferredResult responseWriter = new DeferredResult<>(); + if (quotaExceeded(request, responseWriter)) { + return responseWriter; + } + transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + TransportService transportService = transportContext.getTransportService(); + transportService.process(sessionInfo, JsonConverter.convertToAttributesProto(new JsonParser().parse(json)), + new HttpOkCallback(responseWriter)); + })); + return responseWriter; + } + + @RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST) + public DeferredResult postTelemetry(@PathVariable("deviceToken") String deviceToken, + @RequestBody String json, HttpServletRequest request) { + DeferredResult responseWriter = new DeferredResult(); + if (quotaExceeded(request, responseWriter)) { + return responseWriter; + } + transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + TransportService transportService = transportContext.getTransportService(); + transportService.process(sessionInfo, JsonConverter.convertToTelemetryProto(new JsonParser().parse(json)), + new HttpOkCallback(responseWriter)); + })); + return responseWriter; + } + + @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json") + public DeferredResult subscribeToCommands(@PathVariable("deviceToken") String deviceToken, + @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout, + HttpServletRequest httpRequest) { + DeferredResult responseWriter = new DeferredResult<>(); + if (quotaExceeded(httpRequest, responseWriter)) { + return responseWriter; + } + transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + TransportService transportService = transportContext.getTransportService(); + transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), + timeout == 0 ? transportContext.getDefaultTimeout() : timeout); + transportService.process(sessionInfo, SubscribeToRPCMsg.getDefaultInstance(), + new SessionCloseOnErrorCallback(transportService, sessionInfo)); + + })); + return responseWriter; + } + + @RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST) + public DeferredResult replyToCommand(@PathVariable("deviceToken") String deviceToken, + @PathVariable("requestId") Integer requestId, + @RequestBody String json, HttpServletRequest request) { + DeferredResult responseWriter = new DeferredResult(); + if (quotaExceeded(request, responseWriter)) { + return responseWriter; + } + transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + TransportService transportService = transportContext.getTransportService(); + transportService.process(sessionInfo, ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(json).build(), new HttpOkCallback(responseWriter)); + })); + return responseWriter; + } + + @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST) + public DeferredResult postRpcRequest(@PathVariable("deviceToken") String deviceToken, + @RequestBody String json, HttpServletRequest httpRequest) { + DeferredResult responseWriter = new DeferredResult(); + if (quotaExceeded(httpRequest, responseWriter)) { + return responseWriter; + } + transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + JsonObject request = new JsonParser().parse(json).getAsJsonObject(); + TransportService transportService = transportContext.getTransportService(); + transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout()); + transportService.process(sessionInfo, ToServerRpcRequestMsg.newBuilder().setRequestId(0) + .setMethodName(request.get("method").getAsString()) + .setParams(request.get("params").toString()).build(), + new SessionCloseOnErrorCallback(transportService, sessionInfo)); + })); + return responseWriter; + } + + @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json") + public DeferredResult subscribeToAttributes(@PathVariable("deviceToken") String deviceToken, + @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout, + HttpServletRequest httpRequest) { + DeferredResult responseWriter = new DeferredResult<>(); + if (quotaExceeded(httpRequest, responseWriter)) { + return responseWriter; + } + transportContext.getTransportService().process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + TransportService transportService = transportContext.getTransportService(); + transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), + timeout == 0 ? transportContext.getDefaultTimeout() : timeout); + transportService.process(sessionInfo, SubscribeToAttributeUpdatesMsg.getDefaultInstance(), + new SessionCloseOnErrorCallback(transportService, sessionInfo)); + + })); + return responseWriter; + } + + private boolean quotaExceeded(HttpServletRequest request, DeferredResult responseWriter) { + if (transportContext.getQuotaService().isQuotaExceeded(request.getRemoteAddr())) { + log.warn("REST Quota exceeded for [{}] . Disconnect", request.getRemoteAddr()); + responseWriter.setResult(new ResponseEntity<>(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)); + return true; + } else { + return false; + } + } + + private static class DeviceAuthCallback implements TransportServiceCallback { + private final TransportContext transportContext; + private final DeferredResult responseWriter; + private final Consumer onSuccess; + + DeviceAuthCallback(TransportContext transportContext, DeferredResult responseWriter, Consumer onSuccess) { + this.transportContext = transportContext; + this.responseWriter = responseWriter; + this.onSuccess = onSuccess; + } + + @Override + public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { + if (msg.hasDeviceInfo()) { + UUID sessionId = UUID.randomUUID(); + DeviceInfoProto deviceInfoProto = msg.getDeviceInfo(); + SessionInfoProto sessionInfo = SessionInfoProto.newBuilder() + .setNodeId(transportContext.getNodeId()) + .setTenantIdMSB(deviceInfoProto.getTenantIdMSB()) + .setTenantIdLSB(deviceInfoProto.getTenantIdLSB()) + .setDeviceIdMSB(deviceInfoProto.getDeviceIdMSB()) + .setDeviceIdLSB(deviceInfoProto.getDeviceIdLSB()) + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .build(); + onSuccess.accept(sessionInfo); + } else { + responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); + } + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + + private static class SessionCloseOnErrorCallback implements TransportServiceCallback { + private final TransportService transportService; + private final SessionInfoProto sessionInfo; + + SessionCloseOnErrorCallback(TransportService transportService, SessionInfoProto sessionInfo) { + this.transportService = transportService; + this.sessionInfo = sessionInfo; + } + + @Override + public void onSuccess(Void msg) { + } + + @Override + public void onError(Throwable e) { + transportService.deregisterSession(sessionInfo); + } + } + + private static class HttpOkCallback implements TransportServiceCallback { + private final DeferredResult responseWriter; + + public HttpOkCallback(DeferredResult responseWriter) { + this.responseWriter = responseWriter; + } + + @Override + public void onSuccess(Void msg) { + responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK)); + } + + @Override + public void onError(Throwable e) { + responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + + + private static class HttpSessionListener implements SessionMsgListener { + + private final DeferredResult responseWriter; + + HttpSessionListener(DeferredResult responseWriter) { + this.responseWriter = responseWriter; + } + + @Override + public void onGetAttributesResponse(GetAttributeResponseMsg msg) { + responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); + } + + @Override + public void onAttributeUpdate(AttributeUpdateNotificationMsg msg) { + responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); + } + + @Override + public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) { + responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); + } + + @Override + public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg msg) { + responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK)); + } + + @Override + public void onToServerRpcResponse(ToServerRpcResponseMsg msg) { + responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); + } + } +} diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/HttpTransportContext.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/HttpTransportContext.java new file mode 100644 index 0000000000..601c9ee8e8 --- /dev/null +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/HttpTransportContext.java @@ -0,0 +1,40 @@ +/** + * 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.transport.http; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportContext; + +import javax.annotation.PostConstruct; + +/** + * Created by ashvayka on 04.10.18. + */ +@Slf4j +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')") +@Component +public class HttpTransportContext extends TransportContext { + + @Getter + @Value("${transport.http.request_timeout}") + private long defaultTimeout; + +} diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml new file mode 100644 index 0000000000..91f30a6f3d --- /dev/null +++ b/common/transport/mqtt/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + org.thingsboard.common + 2.2.0-SNAPSHOT + transport + + org.thingsboard.common.transport + mqtt + jar + + Thingsboard MQTT Transport Common + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common.transport + transport-api + + + io.netty + netty-all + + + org.springframework + spring-context-support + + + org.springframework + spring-context + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + com.google.guava + guava + + + com.google.code.findbugs + jsr305 + 3.0.1 + true + + + org.springframework.boot + spring-boot-starter-test + test + + + junit + junit + test + + + org.mockito + mockito-all + test + + + + diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java similarity index 69% rename from transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java rename to common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 9c458fd39f..aa643ebc9b 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -20,15 +20,23 @@ import io.netty.handler.ssl.SslHandler; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.dao.EncryptionUtil; -import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.mqtt.util.SslUtil; -import javax.net.ssl.*; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -37,12 +45,15 @@ import java.net.URL; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; /** * Created by valerii.sosliuk on 11/6/16. */ @Slf4j @Component("MqttSslHandlerProvider") +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')") @ConditionalOnProperty(prefix = "mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false) public class MqttSslHandlerProvider { @@ -58,8 +69,7 @@ public class MqttSslHandlerProvider { private String keyStoreType; @Autowired - private DeviceCredentialsService deviceCredentialsService; - + private TransportService transportService; public SslHandler getSslHandler() { try { @@ -112,17 +122,17 @@ public class MqttSslHandlerProvider { break; } } - return new ThingsboardMqttX509TrustManager(x509Tm, deviceCredentialsService); + return new ThingsboardMqttX509TrustManager(x509Tm, transportService); } static class ThingsboardMqttX509TrustManager implements X509TrustManager { private final X509TrustManager trustManager; - private DeviceCredentialsService deviceCredentialsService; + private TransportService transportService; - ThingsboardMqttX509TrustManager(X509TrustManager trustManager, DeviceCredentialsService deviceCredentialsService) { + ThingsboardMqttX509TrustManager(X509TrustManager trustManager, TransportService transportService) { this.trustManager = trustManager; - this.deviceCredentialsService = deviceCredentialsService; + this.transportService = transportService; } @Override @@ -139,20 +149,39 @@ public class MqttSslHandlerProvider { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - DeviceCredentials deviceCredentials = null; + String credentialsBody = null; for (X509Certificate cert : chain) { try { String strCert = SslUtil.getX509CertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(sha3Hash); - if (deviceCredentials != null && strCert.equals(deviceCredentials.getCredentialsValue())) { + final String[] credentialsBodyHolder = new String[1]; + CountDownLatch latch = new CountDownLatch(1); + transportService.process(TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback() { + @Override + public void onSuccess(TransportProtos.ValidateDeviceCredentialsResponseMsg msg) { + if (!StringUtils.isEmpty(msg.getCredentialsBody())) { + credentialsBodyHolder[0] = msg.getCredentialsBody(); + } + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + if (strCert.equals(credentialsBodyHolder[0])) { + credentialsBody = credentialsBodyHolder[0]; break; } - } catch (IOException e) { + } catch (InterruptedException | IOException e) { log.error(e.getMessage(), e); } } - if (deviceCredentials == null) { + if (credentialsBody == null) { throw new CertificateException("Invalid Device Certificate"); } } diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTopics.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTopics.java similarity index 100% rename from transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTopics.java rename to common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTopics.java diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java new file mode 100644 index 0000000000..538daa1f9c --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java @@ -0,0 +1,64 @@ +/** + * 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.transport.mqtt; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.handler.ssl.SslHandler; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; +import org.thingsboard.server.kafka.TbNodeIdProvider; +import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by ashvayka on 04.10.18. + */ +@Slf4j +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.mqtt.enabled}'=='true')") +@Component +public class MqttTransportContext extends TransportContext { + + @Getter + @Autowired(required = false) + private MqttSslHandlerProvider sslHandlerProvider; + + @Getter + @Autowired + private MqttTransportAdaptor adaptor; + + @Getter + @Value("${transport.mqtt.netty.max_payload_size}") + private Integer maxPayloadSize; + + @Getter + @Setter + private SslHandler sslHandler; + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java new file mode 100644 index 0000000000..46a890f19e --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -0,0 +1,558 @@ +/** + * 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.transport.mqtt; + +import com.fasterxml.jackson.databind.JsonNode; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.codec.mqtt.MqttConnAckMessage; +import io.netty.handler.codec.mqtt.MqttConnAckVariableHeader; +import io.netty.handler.codec.mqtt.MqttConnectMessage; +import io.netty.handler.codec.mqtt.MqttConnectReturnCode; +import io.netty.handler.codec.mqtt.MqttFixedHeader; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; +import io.netty.handler.codec.mqtt.MqttPubAckMessage; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import io.netty.handler.codec.mqtt.MqttQoS; +import io.netty.handler.codec.mqtt.MqttSubAckMessage; +import io.netty.handler.codec.mqtt.MqttSubAckPayload; +import io.netty.handler.codec.mqtt.MqttSubscribeMessage; +import io.netty.handler.codec.mqtt.MqttTopicSubscription; +import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; +import io.netty.handler.ssl.SslHandler; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.quota.QuotaService; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; +import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; +import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; +import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; +import org.thingsboard.server.transport.mqtt.util.SslUtil; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.security.cert.X509Certificate; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD; +import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; +import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; +import static io.netty.handler.codec.mqtt.MqttMessageType.PINGRESP; +import static io.netty.handler.codec.mqtt.MqttMessageType.PUBACK; +import static io.netty.handler.codec.mqtt.MqttMessageType.SUBACK; +import static io.netty.handler.codec.mqtt.MqttMessageType.UNSUBACK; +import static io.netty.handler.codec.mqtt.MqttQoS.AT_LEAST_ONCE; +import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE; +import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener>, SessionMsgListener { + + private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE; + + private final UUID sessionId; + private final MqttTransportContext context; + private final MqttTransportAdaptor adaptor; + private final TransportService transportService; + private final QuotaService quotaService; + private final SslHandler sslHandler; + private final ConcurrentMap mqttQoSMap; + + private volatile SessionInfoProto sessionInfo; + private volatile InetSocketAddress address; + private volatile DeviceSessionCtx deviceSessionCtx; + private volatile GatewaySessionHandler gatewaySessionHandler; + + MqttTransportHandler(MqttTransportContext context) { + this.sessionId = UUID.randomUUID(); + this.context = context; + this.transportService = context.getTransportService(); + this.adaptor = context.getAdaptor(); + this.quotaService = context.getQuotaService(); + this.sslHandler = context.getSslHandler(); + this.mqttQoSMap = new ConcurrentHashMap<>(); + this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + log.trace("[{}] Processing msg: {}", sessionId, msg); + if (msg instanceof MqttMessage) { + processMqttMsg(ctx, (MqttMessage) msg); + } else { + ctx.close(); + } + } + + private void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) { + address = (InetSocketAddress) ctx.channel().remoteAddress(); + if (msg.fixedHeader() == null) { + log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort()); + processDisconnect(ctx); + return; + } + + if (quotaService.isQuotaExceeded(address.getHostName())) { + log.warn("MQTT Quota exceeded for [{}:{}] . Disconnect", address.getHostName(), address.getPort()); + processDisconnect(ctx); + return; + } + + deviceSessionCtx.setChannel(ctx); + switch (msg.fixedHeader().messageType()) { + case CONNECT: + processConnect(ctx, (MqttConnectMessage) msg); + break; + case PUBLISH: + processPublish(ctx, (MqttPublishMessage) msg); + break; + case SUBSCRIBE: + processSubscribe(ctx, (MqttSubscribeMessage) msg); + break; + case UNSUBSCRIBE: + processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg); + break; + case PINGREQ: + //TODO: should we push the notification to the rule engine? + if (checkConnected(ctx)) { + ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); + } + break; + case DISCONNECT: + if (checkConnected(ctx)) { + processDisconnect(ctx); + } + break; + default: + break; + } + + } + + private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) { + if (!checkConnected(ctx)) { + return; + } + String topicName = mqttMsg.variableHeader().topicName(); + int msgId = mqttMsg.variableHeader().packetId(); + log.trace("[{}] Processing publish msg [{}][{}]!", sessionId, topicName, msgId); + + if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { + if (gatewaySessionHandler != null) { + handleGatewayPublishMsg(topicName, msgId, mqttMsg); + } + } else { + processDevicePublish(ctx, mqttMsg, topicName, msgId); + } + } + + private void handleGatewayPublishMsg(String topicName, int msgId, MqttPublishMessage mqttMsg) { + try { + switch (topicName) { + case MqttTopics.GATEWAY_TELEMETRY_TOPIC: + gatewaySessionHandler.onDeviceTelemetry(mqttMsg); + break; + case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: + gatewaySessionHandler.onDeviceAttributes(mqttMsg); + break; + case MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC: + gatewaySessionHandler.onDeviceAttributesRequest(mqttMsg); + break; + case MqttTopics.GATEWAY_RPC_TOPIC: + gatewaySessionHandler.onDeviceRpcResponse(mqttMsg); + break; + case MqttTopics.GATEWAY_CONNECT_TOPIC: + gatewaySessionHandler.onDeviceConnect(mqttMsg); + break; + case MqttTopics.GATEWAY_DISCONNECT_TOPIC: + gatewaySessionHandler.onDeviceDisconnect(mqttMsg); + break; + } + } catch (RuntimeException | AdaptorException e) { + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + } + } + + private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { + try { + if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_TOPIC)) { + TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); + transportService.process(sessionInfo, postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); + } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_TOPIC)) { + TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); + transportService.process(sessionInfo, postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); + } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { + TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg); + transportService.process(sessionInfo, getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC)) { + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = adaptor.convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg); + transportService.process(sessionInfo, rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); + } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC)) { + TransportProtos.ToServerRpcRequestMsg rpcRequestMsg = adaptor.convertToServerRpcRequest(deviceSessionCtx, mqttMsg); + transportService.process(sessionInfo, rpcRequestMsg, getPubAckCallback(ctx, msgId, rpcRequestMsg)); + } + } catch (AdaptorException e) { + log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId); + ctx.close(); + } + } + + private TransportServiceCallback getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { + return new TransportServiceCallback() { + @Override + public void onSuccess(Void dummy) { + log.trace("[{}] Published msg: {}", sessionId, msg); + if (msgId > 0) { + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); + } + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); + processDisconnect(ctx); + } + }; + } + + private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { + if (!checkConnected(ctx)) { + return; + } + log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); + List grantedQoSList = new ArrayList<>(); + for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) { + String topic = subscription.topicName(); + MqttQoS reqQoS = subscription.qualityOfService(); + try { + switch (topic) { + case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: { + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); + registerSubQoS(topic, grantedQoSList, reqQoS); + break; + } + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC: { + transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null); + registerSubQoS(topic, grantedQoSList, reqQoS); + break; + } + case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC: + case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC: + case MqttTopics.GATEWAY_RPC_TOPIC: + case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC: + registerSubQoS(topic, grantedQoSList, reqQoS); + break; + default: + log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); + grantedQoSList.add(FAILURE.value()); + break; + } + } catch (Exception e) { + log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); + grantedQoSList.add(FAILURE.value()); + } + } + ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList)); + } + + private void registerSubQoS(String topic, List grantedQoSList, MqttQoS reqQoS) { + grantedQoSList.add(getMinSupportedQos(reqQoS)); + mqttQoSMap.put(new MqttTopicMatcher(topic), getMinSupportedQos(reqQoS)); + } + + private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { + if (!checkConnected(ctx)) { + return; + } + log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); + for (String topicName : mqttMsg.payload().topics()) { + mqttQoSMap.remove(new MqttTopicMatcher(topicName)); + try { + switch (topicName) { + case MqttTopics.DEVICE_ATTRIBUTES_TOPIC: { + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), null); + break; + } + case MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC: { + transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), null); + break; + } + } + } catch (Exception e) { + log.warn("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName); + } + } + ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId())); + } + + private MqttMessage createUnSubAckMessage(int msgId) { + MqttFixedHeader mqttFixedHeader = + new MqttFixedHeader(UNSUBACK, false, AT_LEAST_ONCE, false, 0); + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); + return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader); + } + + private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { + log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); + X509Certificate cert; + if (sslHandler != null && (cert = getX509Certificate()) != null) { + processX509CertConnect(ctx, cert); + } else { + processAuthTokenConnect(ctx, msg); + } + } + + private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { + String userName = msg.payload().userName(); + if (StringUtils.isEmpty(userName)) { + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)); + ctx.close(); + } else { + transportService.process(ValidateDeviceTokenRequestMsg.newBuilder().setToken(userName).build(), + new TransportServiceCallback() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { + onValidateDeviceResponse(msg, ctx); + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to process credentials: {}", address, userName, e); + ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); + ctx.close(); + } + }); + } + } + + private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) { + try { + String strCert = SslUtil.getX509CertificateString(cert); + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); + transportService.process(ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), + new TransportServiceCallback() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { + onValidateDeviceResponse(msg, ctx); + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to process credentials: {}", address, sha3Hash, e); + ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); + ctx.close(); + } + }); + } catch (Exception e) { + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); + ctx.close(); + } + } + + private X509Certificate getX509Certificate() { + try { + X509Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificateChain(); + if (certChain.length > 0) { + return certChain[0]; + } + } catch (SSLPeerUnverifiedException e) { + log.warn(e.getMessage()); + return null; + } + return null; + } + + private void processDisconnect(ChannelHandlerContext ctx) { + ctx.close(); + if (deviceSessionCtx.isConnected()) { + transportService.process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null); + transportService.deregisterSession(sessionInfo); + if (gatewaySessionHandler != null) { + gatewaySessionHandler.onGatewayDisconnect(); + } + } + } + + private MqttConnAckMessage createMqttConnAckMsg(MqttConnectReturnCode returnCode) { + MqttFixedHeader mqttFixedHeader = + new MqttFixedHeader(CONNACK, false, AT_MOST_ONCE, false, 0); + MqttConnAckVariableHeader mqttConnAckVariableHeader = + new MqttConnAckVariableHeader(returnCode, true); + return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + log.error("[{}] Unexpected Exception", sessionId, cause); + ctx.close(); + } + + private static MqttSubAckMessage createSubAckMessage(Integer msgId, List grantedQoSList) { + MqttFixedHeader mqttFixedHeader = + new MqttFixedHeader(SUBACK, false, AT_LEAST_ONCE, false, 0); + MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); + MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList); + return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload); + } + + private static int getMinSupportedQos(MqttQoS reqQoS) { + return Math.min(reqQoS.value(), MAX_SUPPORTED_QOS_LVL.value()); + } + + public static MqttPubAckMessage createMqttPubAckMsg(int requestId) { + MqttFixedHeader mqttFixedHeader = + new MqttFixedHeader(PUBACK, false, AT_LEAST_ONCE, false, 0); + MqttMessageIdVariableHeader mqttMsgIdVariableHeader = + MqttMessageIdVariableHeader.from(requestId); + return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader); + } + + private boolean checkConnected(ChannelHandlerContext ctx) { + if (deviceSessionCtx.isConnected()) { + return true; + } else { + log.info("[{}] Closing current session due to invalid msg order [{}][{}]", sessionId); + ctx.close(); + return false; + } + } + + private void checkGatewaySession() { + DeviceInfoProto device = deviceSessionCtx.getDeviceInfo(); + try { + JsonNode infoNode = context.getMapper().readTree(device.getAdditionalInfo()); + if (infoNode != null) { + JsonNode gatewayNode = infoNode.get("gateway"); + if (gatewayNode != null && gatewayNode.asBoolean()) { + gatewaySessionHandler = new GatewaySessionHandler(context, deviceSessionCtx, sessionId); + } + } + } catch (IOException e) { + log.trace("[{}][{}] Failed to fetch device additional info", sessionId, device.getDeviceName(), e); + } + } + + public static SessionEventMsg getSessionEventMsg(SessionEvent event) { + return SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(event).build(); + } + + @Override + public void operationComplete(Future future) throws Exception { + if (deviceSessionCtx.isConnected()) { + transportService.process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null); + transportService.deregisterSession(sessionInfo); + } + } + + private void onValidateDeviceResponse(ValidateDeviceCredentialsResponseMsg msg, ChannelHandlerContext ctx) { + if (!msg.hasDeviceInfo()) { + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); + ctx.close(); + } else { + deviceSessionCtx.setDeviceInfo(msg.getDeviceInfo()); + sessionInfo = SessionInfoProto.newBuilder() + .setNodeId(context.getNodeId()) + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .build(); + transportService.process(sessionInfo, getSessionEventMsg(SessionEvent.OPEN), null); + transportService.registerAsyncSession(sessionInfo, this); + checkGatewaySession(); + ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); + } + } + + @Override + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { + try { + adaptor.convertToPublish(deviceSessionCtx, response).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) { + try { + adaptor.convertToPublish(deviceSessionCtx, notification).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes update to MQTT msg", sessionId, e); + } + } + + @Override + public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { + log.trace("[{}] Received the remote command to close the session", sessionId); + processDisconnect(deviceSessionCtx.getChannel()); + } + + @Override + public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { + log.trace("[{}] Received RPC command to device", sessionId); + try { + adaptor.convertToPublish(deviceSessionCtx, rpcRequest).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device RPC commandto MQTT msg", sessionId, e); + } + } + + @Override + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg rpcResponse) { + log.trace("[{}] Received RPC command to device", sessionId); + try { + adaptor.convertToPublish(deviceSessionCtx, rpcResponse).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device RPC commandto MQTT msg", sessionId, e); + } + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java new file mode 100644 index 0000000000..550837842f --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java @@ -0,0 +1,54 @@ +/** + * 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.transport.mqtt; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.mqtt.MqttDecoder; +import io.netty.handler.codec.mqtt.MqttEncoder; +import io.netty.handler.ssl.SslHandler; + +/** + * @author Andrew Shvayka + */ +public class MqttTransportServerInitializer extends ChannelInitializer { + + private final MqttTransportContext context; + + public MqttTransportServerInitializer(MqttTransportContext context) { + this.context = context; + } + + @Override + public void initChannel(SocketChannel ch) { + ChannelPipeline pipeline = ch.pipeline(); + SslHandler sslHandler = null; + if (context.getSslHandlerProvider() != null) { + sslHandler = context.getSslHandlerProvider().getSslHandler(); + pipeline.addLast(sslHandler); + context.setSslHandler(sslHandler); + } + pipeline.addLast("decoder", new MqttDecoder(context.getMaxPayloadSize())); + pipeline.addLast("encoder", MqttEncoder.INSTANCE); + + MqttTransportHandler handler = new MqttTransportHandler(context); + + pipeline.addLast(handler); + ch.closeFuture().addListener(handler); + } + +} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java similarity index 58% rename from transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java rename to common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java index bb8d4ad54c..55df31a735 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java @@ -24,15 +24,9 @@ import io.netty.util.ResourceLeakDetector; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -41,51 +35,24 @@ import javax.annotation.PreDestroy; * @author Andrew Shvayka */ @Service("MqttTransportService") -@ConditionalOnProperty(prefix = "mqtt", value = "enabled", havingValue = "true", matchIfMissing = false) +@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.mqtt.enabled}'=='true')") @Slf4j public class MqttTransportService { - private static final String V1 = "v1"; - private static final String DEVICE = "device"; - - @Autowired(required = false) - private ApplicationContext appContext; - - @Autowired(required = false) - private SessionMsgProcessor processor; - - @Autowired(required = false) - private DeviceService deviceService; - - @Autowired(required = false) - private DeviceAuthService authService; - - @Autowired(required = false) - private RelationService relationService; - - @Autowired(required = false) - private MqttSslHandlerProvider sslHandlerProvider; - - @Autowired(required = false) - private HostRequestsQuotaService quotaService; - - @Value("${mqtt.bind_address}") + @Value("${transport.mqtt.bind_address}") private String host; - @Value("${mqtt.bind_port}") + @Value("${transport.mqtt.bind_port}") private Integer port; - @Value("${mqtt.adaptor}") - private String adaptorName; - @Value("${mqtt.netty.leak_detector_level}") + @Value("${transport.mqtt.netty.leak_detector_level}") private String leakDetectorLevel; - @Value("${mqtt.netty.boss_group_thread_count}") + @Value("${transport.mqtt.netty.boss_group_thread_count}") private Integer bossGroupThreadCount; - @Value("${mqtt.netty.worker_group_thread_count}") + @Value("${transport.mqtt.netty.worker_group_thread_count}") private Integer workerGroupThreadCount; - @Value("${mqtt.netty.max_payload_size}") - private Integer maxPayloadSize; - private MqttTransportAdaptor adaptor; + @Autowired + private MqttTransportContext context; private Channel serverChannel; private EventLoopGroup bossGroup; @@ -97,17 +64,12 @@ public class MqttTransportService { ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.valueOf(leakDetectorLevel.toUpperCase())); log.info("Starting MQTT transport..."); - log.info("Lookup MQTT transport adaptor {}", adaptorName); - this.adaptor = (MqttTransportAdaptor) appContext.getBean(adaptorName); - - log.info("Starting MQTT transport server"); bossGroup = new NioEventLoopGroup(bossGroupThreadCount); workerGroup = new NioEventLoopGroup(workerGroupThreadCount); ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) - .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService, - adaptor, sslHandlerProvider, quotaService, maxPayloadSize)); + .childHandler(new MqttTransportServerInitializer(context)); serverChannel = b.bind(host, port).sync().channel(); log.info("Mqtt transport started!"); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java new file mode 100644 index 0000000000..8393ca9e0c --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -0,0 +1,217 @@ +/** + * 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.transport.mqtt.adaptors; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.netty.handler.codec.mqtt.MqttFixedHeader; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.MqttTopics; +import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Component("JsonMqttAdaptor") +@Slf4j +public class JsonMqttAdaptor implements MqttTransportAdaptor { + + private static final Gson GSON = new Gson(); + private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); + + @Override + public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + String payload = validatePayload(ctx.getSessionId(), inbound.payload()); + try { + return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + String payload = validatePayload(ctx.getSessionId(), inbound.payload()); + try { + return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + String topicName = inbound.variableHeader().topicName(); + try { + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); + result.setRequestId(Integer.valueOf(topicName.substring(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX.length()))); + String payload = inbound.payload().toString(UTF8); + JsonElement requestBody = new JsonParser().parse(payload); + Set clientKeys = toStringSet(requestBody, "clientKeys"); + Set sharedKeys = toStringSet(requestBody, "sharedKeys"); + if (clientKeys != null) { + result.addAllClientAttributeNames(clientKeys); + } + if (sharedKeys != null) { + result.addAllSharedAttributeNames(sharedKeys); + } + return result.build(); + } catch (RuntimeException e) { + log.warn("Failed to decode get attributes request", e); + throw new AdaptorException(e); + } + } + + @Override + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + String topicName = inbound.variableHeader().topicName(); + try { + Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC.length())); + String payload = inbound.payload().toString(UTF8); + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); + } catch (RuntimeException e) { + log.warn("Failed to decode get attributes request", e); + throw new AdaptorException(e); + } + } + + @Override + public TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + String topicName = inbound.variableHeader().topicName(); + String payload = validatePayload(ctx.getSessionId(), inbound.payload()); + try { + Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC.length())); + return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId); + } catch (IllegalStateException | JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { + if (!StringUtils.isEmpty(responseMsg.getError())) { + throw new AdaptorException(responseMsg.getError()); + } else { + Integer requestId = responseMsg.getRequestId(); + if (requestId >= 0) { + return Optional.of(createMqttPublishMsg(ctx, + MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + requestId, + JsonConverter.toJson(responseMsg))); + } + return Optional.empty(); + } + } + + @Override + public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { + if (!StringUtils.isEmpty(responseMsg.getError())) { + throw new AdaptorException(responseMsg.getError()); + } else { + JsonObject result = JsonConverter.getJsonObjectForGateway(responseMsg); + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result)); + } + } + + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_ATTRIBUTES_TOPIC, JsonConverter.toJson(notificationMsg))); + } + + @Override + public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException { + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, notificationMsg); + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, result)); + } + + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), JsonConverter.toJson(rpcRequest, false))); + } + + @Override + public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_RPC_TOPIC, JsonConverter.toGatewayJson(deviceName, rpcRequest))); + } + + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToServerRpcResponseMsg rpcResponse) { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), JsonConverter.toJson(rpcResponse))); + } + + private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, JsonElement json) { + MqttFixedHeader mqttFixedHeader = + new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); + ByteBuf payload = ALLOCATOR.buffer(); + payload.writeBytes(GSON.toJson(json).getBytes(UTF8)); + return new MqttPublishMessage(mqttFixedHeader, header, payload); + } + + private Set toStringSet(JsonElement requestBody, String name) { + JsonElement element = requestBody.getAsJsonObject().get(name); + if (element != null) { + return new HashSet<>(Arrays.asList(element.getAsString().split(","))); + } else { + return null; + } + } + + public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { + String payload = validatePayload(sessionId, payloadData); + try { + return new JsonParser().parse(payload); + } catch (JsonSyntaxException ex) { + throw new AdaptorException(ex); + } + } + + private static String validatePayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { + try { + String payload = payloadData.toString(UTF8); + if (payload == null) { + log.warn("[{}] Payload is empty!", sessionId); + throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); + } + return payload; + } finally { + payloadData.release(); + } + } + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java new file mode 100644 index 0000000000..7af0e66e34 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -0,0 +1,62 @@ +/** + * 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.transport.mqtt.adaptors; + +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; +import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +public interface MqttTransportAdaptor { + + PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; + + PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; + + GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; + + ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException; + + ToServerRpcRequestMsg convertToServerRpcRequest(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException; + + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg) throws AdaptorException; + + Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg) throws AdaptorException; + + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; + + Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; + + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; + + Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; + + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ToServerRpcResponseMsg rpcResponse) throws AdaptorException; +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java new file mode 100644 index 0000000000..fb05e0a624 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -0,0 +1,47 @@ +/** + * 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.transport.mqtt.session; + +import io.netty.channel.ChannelHandlerContext; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author Andrew Shvayka + */ +@Slf4j +public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { + + @Getter + private ChannelHandlerContext channel; + private AtomicInteger msgIdSeq = new AtomicInteger(0); + + public DeviceSessionCtx(UUID sessionId, ConcurrentMap mqttQoSMap) { + super(sessionId, mqttQoSMap); + } + + public void setChannel(ChannelHandlerContext channel) { + this.channel = channel; + } + + public int nextMsgId() { + return msgIdSeq.incrementAndGet(); + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java new file mode 100644 index 0000000000..38c1ffff3a --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java @@ -0,0 +1,101 @@ +/** + * 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.transport.mqtt.session; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; + +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; + +/** + * Created by ashvayka on 19.01.17. + */ +@Slf4j +public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext implements SessionMsgListener { + + private final GatewaySessionHandler parent; + private final SessionInfoProto sessionInfo; + + public GatewayDeviceSessionCtx(GatewaySessionHandler parent, DeviceInfoProto deviceInfo, ConcurrentMap mqttQoSMap) { + super(UUID.randomUUID(), mqttQoSMap); + this.parent = parent; + this.sessionInfo = SessionInfoProto.newBuilder() + .setNodeId(parent.getNodeId()) + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setDeviceIdMSB(deviceInfo.getDeviceIdMSB()) + .setDeviceIdLSB(deviceInfo.getDeviceIdLSB()) + .setTenantIdMSB(deviceInfo.getTenantIdMSB()) + .setTenantIdLSB(deviceInfo.getTenantIdLSB()) + .build(); + setDeviceInfo(deviceInfo); + } + + @Override + public UUID getSessionId() { + return sessionId; + } + + @Override + public int nextMsgId() { + return parent.nextMsgId(); + } + + SessionInfoProto getSessionInfo() { + return sessionInfo; + } + + @Override + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { + try { + parent.getAdaptor().convertToGatewayPublish(this, response).ifPresent(parent::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) { + try { + parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { + parent.deregisterSession(getDeviceInfo().getDeviceName()); + } + + @Override + public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg request) { + try { + parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(parent::writeAndFlush); + } catch (Exception e) { + log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); + } + } + + @Override + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { + // This feature is not supported in the TB IoT Gateway yet. + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java new file mode 100644 index 0000000000..d6000591a8 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java @@ -0,0 +1,370 @@ +/** + * 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.transport.mqtt.session; + + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.transport.mqtt.MqttTransportContext; +import org.thingsboard.server.transport.mqtt.MqttTransportHandler; +import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; +import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * Created by ashvayka on 19.01.17. + */ +@Slf4j +public class GatewaySessionHandler { + + private static final String DEFAULT_DEVICE_TYPE = "default"; + private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; + private static final String DEVICE_PROPERTY = "device"; + + private final MqttTransportContext context; + private final TransportService transportService; + private final DeviceInfoProto gateway; + private final UUID sessionId; + private final Map devices; + private final ConcurrentMap mqttQoSMap; + private final ChannelHandlerContext channel; + private final DeviceSessionCtx deviceSessionCtx; + + public GatewaySessionHandler(MqttTransportContext context, DeviceSessionCtx deviceSessionCtx, UUID sessionId) { + this.context = context; + this.transportService = context.getTransportService(); + this.deviceSessionCtx = deviceSessionCtx; + this.gateway = deviceSessionCtx.getDeviceInfo(); + this.sessionId = sessionId; + this.devices = new ConcurrentHashMap<>(); + this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap(); + this.channel = deviceSessionCtx.getChannel(); + } + + public void onDeviceConnect(MqttPublishMessage msg) throws AdaptorException { + JsonElement json = getJson(msg); + String deviceName = checkDeviceName(getDeviceName(json)); + String deviceType = getDeviceType(json); + log.trace("[{}] onDeviceConnect: {}", sessionId, deviceName); + Futures.addCallback(onDeviceConnect(deviceName, deviceType), new FutureCallback() { + @Override + public void onSuccess(@Nullable GatewayDeviceSessionCtx result) { + ack(msg); + log.trace("[{}] onDeviceConnectOk: {}", sessionId, deviceName); + } + + @Override + public void onFailure(Throwable t) { + log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, t); + + } + }, context.getExecutor()); + } + + private ListenableFuture onDeviceConnect(String deviceName, String deviceType) { + SettableFuture future = SettableFuture.create(); + GatewayDeviceSessionCtx result = devices.get(deviceName); + if (result == null) { + transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder() + .setDeviceName(deviceName) + .setDeviceType(deviceType) + .setGatewayIdMSB(gateway.getDeviceIdMSB()) + .setGatewayIdLSB(gateway.getDeviceIdLSB()).build(), + new TransportServiceCallback() { + @Override + public void onSuccess(GetOrCreateDeviceFromGatewayResponseMsg msg) { + GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), mqttQoSMap); + if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) { + SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo(); + transportService.process(deviceSessionInfo, MqttTransportHandler.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); + transportService.process(deviceSessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), null); + transportService.process(deviceSessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), null); + transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx); + } + future.set(devices.get(deviceName)); + } + + @Override + public void onError(Throwable e) { + log.warn("[{}] Failed to process device connect command: {}", sessionId, deviceName, e); + future.setException(e); + } + }); + } else { + future.set(result); + } + return future; + } + + public void onDeviceDisconnect(MqttPublishMessage msg) throws AdaptorException { + String deviceName = checkDeviceName(getDeviceName(getJson(msg))); + deregisterSession(deviceName); + ack(msg); + } + + void deregisterSession(String deviceName) { + GatewayDeviceSessionCtx deviceSessionCtx = devices.remove(deviceName); + if (deviceSessionCtx != null) { + deregisterSession(deviceName, deviceSessionCtx); + } else { + log.debug("[{}] Device [{}] was already removed from the gateway session", sessionId, deviceName); + } + } + + public void onGatewayDisconnect() { + devices.forEach(this::deregisterSession); + } + + public void onDeviceTelemetry(MqttPublishMessage mqttMsg) throws AdaptorException { + JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); + int msgId = mqttMsg.variableHeader().packetId(); + if (json.isJsonObject()) { + JsonObject jsonObj = json.getAsJsonObject(); + for (Map.Entry deviceEntry : jsonObj.entrySet()) { + String deviceName = deviceEntry.getKey(); + Futures.addCallback(checkDeviceConnected(deviceName), + new FutureCallback() { + @Override + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) { + if (!deviceEntry.getValue().isJsonArray()) { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); + } + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(deviceEntry.getValue().getAsJsonArray()); + transportService.process(deviceCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(channel, deviceName, msgId, postTelemetryMsg)); + } + + @Override + public void onFailure(Throwable t) { + log.debug("[{}] Failed to process device teleemtry command: {}", sessionId, deviceName, t); + } + }, context.getExecutor()); + } + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); + } + } + + public void onDeviceAttributes(MqttPublishMessage mqttMsg) throws AdaptorException { + JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); + int msgId = mqttMsg.variableHeader().packetId(); + if (json.isJsonObject()) { + JsonObject jsonObj = json.getAsJsonObject(); + for (Map.Entry deviceEntry : jsonObj.entrySet()) { + String deviceName = deviceEntry.getKey(); + Futures.addCallback(checkDeviceConnected(deviceName), + new FutureCallback() { + @Override + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) { + if (!deviceEntry.getValue().isJsonObject()) { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); + } + TransportProtos.PostAttributeMsg postAttributeMsg = JsonConverter.convertToAttributesProto(deviceEntry.getValue().getAsJsonObject()); + transportService.process(deviceCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(channel, deviceName, msgId, postAttributeMsg)); + } + + @Override + public void onFailure(Throwable t) { + log.debug("[{}] Failed to process device attributes command: {}", sessionId, deviceName, t); + } + }, context.getExecutor()); + } + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); + } + } + + public void onDeviceRpcResponse(MqttPublishMessage mqttMsg) throws AdaptorException { + JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); + int msgId = mqttMsg.variableHeader().packetId(); + if (json.isJsonObject()) { + JsonObject jsonObj = json.getAsJsonObject(); + String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); + Futures.addCallback(checkDeviceConnected(deviceName), + new FutureCallback() { + @Override + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) { + Integer requestId = jsonObj.get("id").getAsInt(); + String data = jsonObj.get("data").toString(); + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() + .setRequestId(requestId).setPayload(data).build(); + transportService.process(deviceCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(channel, deviceName, msgId, rpcResponseMsg)); + } + + @Override + public void onFailure(Throwable t) { + log.debug("[{}] Failed to process device teleemtry command: {}", sessionId, deviceName, t); + } + }, context.getExecutor()); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); + } + } + + public void onDeviceAttributesRequest(MqttPublishMessage msg) throws AdaptorException { + JsonElement json = JsonMqttAdaptor.validateJsonPayload(sessionId, msg.payload()); + if (json.isJsonObject()) { + JsonObject jsonObj = json.getAsJsonObject(); + int requestId = jsonObj.get("id").getAsInt(); + String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); + boolean clientScope = jsonObj.get("client").getAsBoolean(); + Set keys; + if (jsonObj.has("key")) { + keys = Collections.singleton(jsonObj.get("key").getAsString()); + } else { + JsonArray keysArray = jsonObj.get("keys").getAsJsonArray(); + keys = new HashSet<>(); + for (JsonElement keyObj : keysArray) { + keys.add(keyObj.getAsString()); + } + } + TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); + result.setRequestId(requestId); + + if (clientScope) { + result.addAllClientAttributeNames(keys); + } else { + result.addAllSharedAttributeNames(keys); + } + TransportProtos.GetAttributeRequestMsg requestMsg = result.build(); + int msgId = msg.variableHeader().packetId(); + Futures.addCallback(checkDeviceConnected(deviceName), + new FutureCallback() { + @Override + public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) { + transportService.process(deviceCtx.getSessionInfo(), requestMsg, getPubAckCallback(channel, deviceName, msgId, requestMsg)); + } + + @Override + public void onFailure(Throwable t) { + log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t); + } + }, context.getExecutor()); + ack(msg); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); + } + } + + private ListenableFuture checkDeviceConnected(String deviceName) { + GatewayDeviceSessionCtx ctx = devices.get(deviceName); + if (ctx == null) { + log.debug("[{}] Missing device [{}] for the gateway session", sessionId, deviceName); + return onDeviceConnect(deviceName, DEFAULT_DEVICE_TYPE); + } else { + return Futures.immediateFuture(ctx); + } + } + + private String checkDeviceName(String deviceName) { + if (StringUtils.isEmpty(deviceName)) { + throw new RuntimeException("Device name is empty!"); + } else { + return deviceName; + } + } + + private String getDeviceName(JsonElement json) throws AdaptorException { + return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString(); + } + + private String getDeviceType(JsonElement json) throws AdaptorException { + JsonElement type = json.getAsJsonObject().get("type"); + return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString(); + } + + private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException { + return JsonMqttAdaptor.validateJsonPayload(sessionId, mqttMsg.payload()); + } + + private void ack(MqttPublishMessage msg) { + if (msg.variableHeader().packetId() > 0) { + writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().packetId())); + } + } + + void writeAndFlush(MqttMessage mqttMessage) { + channel.writeAndFlush(mqttMessage); + } + + public String getNodeId() { + return context.getNodeId(); + } + + private void deregisterSession(String deviceName, GatewayDeviceSessionCtx deviceSessionCtx) { + transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); + transportService.process(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); + log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName); + } + + private TransportServiceCallback getPubAckCallback(final ChannelHandlerContext ctx, final String deviceName, final int msgId, final T msg) { + return new TransportServiceCallback() { + @Override + public void onSuccess(Void dummy) { + log.trace("[{}][{}] Published msg: {}", sessionId, deviceName, msg); + if (msgId > 0) { + ctx.writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msgId)); + } + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to publish msg: {}", sessionId, deviceName, msg, e); + ctx.close(); + } + }; + } + + public MqttTransportContext getContext() { + return context; + } + + public MqttTransportAdaptor getAdaptor() { + return context.getAdaptor(); + } + + public int nextMsgId() { + return deviceSessionCtx.nextMsgId(); + } +} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java similarity index 70% rename from transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java rename to common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java index 0752331f5a..745b0ed444 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java @@ -16,14 +16,11 @@ package org.thingsboard.server.transport.mqtt.session; import io.netty.handler.codec.mqtt.MqttQoS; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; -import org.thingsboard.server.transport.mqtt.MqttTopicMatcher; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.stream.Collectors; @@ -34,13 +31,8 @@ public abstract class MqttDeviceAwareSessionContext extends DeviceAwareSessionCo private final ConcurrentMap mqttQoSMap; - public MqttDeviceAwareSessionContext(SessionMsgProcessor processor, DeviceAuthService authService, ConcurrentMap mqttQoSMap) { - super(processor, authService); - this.mqttQoSMap = mqttQoSMap; - } - - public MqttDeviceAwareSessionContext(SessionMsgProcessor processor, DeviceAuthService authService, Device device, ConcurrentMap mqttQoSMap) { - super(processor, authService, device); + public MqttDeviceAwareSessionContext(UUID sessionId, ConcurrentMap mqttQoSMap) { + super(sessionId); this.mqttQoSMap = mqttQoSMap; } diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTopicMatcher.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttTopicMatcher.java similarity index 93% rename from transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTopicMatcher.java rename to common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttTopicMatcher.java index b4a20283f7..4d48223fcd 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTopicMatcher.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttTopicMatcher.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.mqtt; +package org.thingsboard.server.transport.mqtt.session; import java.util.regex.Pattern; @@ -22,7 +22,7 @@ public class MqttTopicMatcher { private final String topic; private final Pattern topicRegex; - MqttTopicMatcher(String topic) { + public MqttTopicMatcher(String topic) { if(topic == null){ throw new NullPointerException("topic"); } diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java similarity index 96% rename from transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java rename to common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java index 96cdf142a6..d98d1b91bc 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.mqtt.util; import lombok.extern.slf4j.Slf4j; import org.springframework.util.Base64Utils; -import org.thingsboard.server.dao.EncryptionUtil; +import org.thingsboard.server.common.msg.EncryptionUtil; import java.io.IOException; import java.security.cert.CertificateEncodingException; diff --git a/common/transport/pom.xml b/common/transport/pom.xml index a349012fce..83e7ba806a 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -16,7 +16,7 @@ --> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.thingsboard @@ -25,67 +25,20 @@ org.thingsboard.common transport - jar + pom - Thingsboard Server Common Transport components + Thingsboard Server Commons https://thingsboard.io UTF-8 ${basedir}/../.. - - - - org.thingsboard.common - data - - - org.thingsboard.common - message - - - com.google.code.gson - gson - - - org.slf4j - slf4j-api - - - org.slf4j - log4j-over-slf4j - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - - junit - junit - test - - - org.mockito - mockito-all - test - - - org.springframework - spring-context - - - com.google.guava - guava - - - org.apache.commons - commons-lang3 - - + + transport-api + mqtt + http + coap + diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java deleted file mode 100644 index 080f874efb..0000000000 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java +++ /dev/null @@ -1,32 +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.common.transport; - -import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; -import org.thingsboard.server.common.msg.session.SessionContext; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; - -import java.util.Optional; - -public interface TransportAdaptor { - - AdaptorToSessionActorMsg convertToActorMsg(C ctx, SessionMsgType type, T inbound) throws AdaptorException; - - Optional convertToAdaptorMsg(C ctx, SessionActorToAdaptorMsg msg) throws AdaptorException; - -} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java deleted file mode 100644 index e7c173485c..0000000000 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ /dev/null @@ -1,216 +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.common.transport.adaptor; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map.Entry; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import com.google.gson.*; -import org.thingsboard.server.common.msg.core.*; - -import org.thingsboard.server.common.data.kv.*; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; - -public class JsonConverter { - - private static final Gson GSON = new Gson(); - public static final String CAN_T_PARSE_VALUE = "Can't parse value: "; - - public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject) throws JsonSyntaxException { - return convertToTelemetry(jsonObject, BasicRequest.DEFAULT_REQUEST_ID); - } - - public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, long ts) throws JsonSyntaxException { - return convertToTelemetry(jsonObject, ts, BasicRequest.DEFAULT_REQUEST_ID); - } - - public static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, int requestId) throws JsonSyntaxException { - return convertToTelemetry(jsonObject, System.currentTimeMillis(), requestId); - } - - private static TelemetryUploadRequest convertToTelemetry(JsonElement jsonObject, long systemTs, int requestId) throws JsonSyntaxException { - BasicTelemetryUploadRequest request = new BasicTelemetryUploadRequest(requestId); - if (jsonObject.isJsonObject()) { - parseObject(request, systemTs, jsonObject); - } else if (jsonObject.isJsonArray()) { - jsonObject.getAsJsonArray().forEach(je -> { - if (je.isJsonObject()) { - parseObject(request, systemTs, je.getAsJsonObject()); - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + je); - } - }); - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonObject); - } - return request; - } - - public static ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { - JsonObject object = json.getAsJsonObject(); - return new ToServerRpcRequestMsg(requestId, object.get("method").getAsString(), GSON.toJson(object.get("params"))); - } - - private static void parseObject(BasicTelemetryUploadRequest request, long systemTs, JsonElement jsonObject) { - JsonObject jo = jsonObject.getAsJsonObject(); - if (jo.has("ts") && jo.has("values")) { - parseWithTs(request, jo); - } else { - parseWithoutTs(request, systemTs, jo); - } - } - - private static void parseWithoutTs(BasicTelemetryUploadRequest request, long systemTs, JsonObject jo) { - for (KvEntry entry : parseValues(jo)) { - request.add(systemTs, entry); - } - } - - public static void parseWithTs(BasicTelemetryUploadRequest request, JsonObject jo) { - long ts = jo.get("ts").getAsLong(); - JsonObject valuesObject = jo.get("values").getAsJsonObject(); - for (KvEntry entry : parseValues(valuesObject)) { - request.add(ts, entry); - } - } - - public static List parseValues(JsonObject valuesObject) { - List result = new ArrayList<>(); - for (Entry valueEntry : valuesObject.entrySet()) { - JsonElement element = valueEntry.getValue(); - if (element.isJsonPrimitive()) { - JsonPrimitive value = element.getAsJsonPrimitive(); - if (value.isString()) { - result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); - } else if (value.isBoolean()) { - result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean())); - } else if (value.isNumber()) { - parseNumericValue(result, valueEntry, value); - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); - } - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); - } - } - return result; - } - - private static void parseNumericValue(List result, Entry valueEntry, JsonPrimitive value) { - if (value.getAsString().contains(".")) { - result.add(new DoubleDataEntry(valueEntry.getKey(), value.getAsDouble())); - } else { - try { - long longValue = Long.parseLong(value.getAsString()); - result.add(new LongDataEntry(valueEntry.getKey(), longValue)); - } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); - } - } - } - - public static AttributesUpdateRequest convertToAttributes(JsonElement element) { - return convertToAttributes(element, BasicRequest.DEFAULT_REQUEST_ID); - } - - public static AttributesUpdateRequest convertToAttributes(JsonElement element, int requestId) { - if (element.isJsonObject()) { - BasicAttributesUpdateRequest request = new BasicAttributesUpdateRequest(requestId); - long ts = System.currentTimeMillis(); - request.add(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); - return request; - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); - } - } - - public static JsonObject toJson(AttributesKVMsg payload, boolean asMap) { - JsonObject result = new JsonObject(); - if (asMap) { - if (!payload.getClientAttributes().isEmpty()) { - JsonObject attrObject = new JsonObject(); - payload.getClientAttributes().forEach(addToObject(attrObject)); - result.add("client", attrObject); - } - if (!payload.getSharedAttributes().isEmpty()) { - JsonObject attrObject = new JsonObject(); - payload.getSharedAttributes().forEach(addToObject(attrObject)); - result.add("shared", attrObject); - } - } else { - payload.getClientAttributes().forEach(addToObject(result)); - payload.getSharedAttributes().forEach(addToObject(result)); - } - if (!payload.getDeletedAttributes().isEmpty()) { - JsonArray attrObject = new JsonArray(); - payload.getDeletedAttributes().forEach(addToObject(attrObject)); - result.add("deleted", attrObject); - } - return result; - } - - private static Consumer addToObject(JsonArray result) { - return key -> { - result.add(key.getAttributeKey()); - }; - } - - private static Consumer addToObject(JsonObject result) { - return de -> { - JsonPrimitive value; - switch (de.getDataType()) { - case BOOLEAN: - value = new JsonPrimitive(de.getBooleanValue().get()); - break; - case DOUBLE: - value = new JsonPrimitive(de.getDoubleValue().get()); - break; - case LONG: - value = new JsonPrimitive(de.getLongValue().get()); - break; - case STRING: - value = new JsonPrimitive(de.getStrValue().get()); - break; - default: - throw new IllegalArgumentException("Unsupported data type: " + de.getDataType()); - } - result.add(de.getKey(), value); - }; - } - - public static JsonObject toJson(ToDeviceRpcRequestMsg msg, boolean includeRequestId) { - JsonObject result = new JsonObject(); - if (includeRequestId) { - result.addProperty("id", msg.getRequestId()); - } - result.addProperty("method", msg.getMethod()); - result.add("params", new JsonParser().parse(msg.getParams())); - return result; - } - - public static JsonElement toJson(ToServerRpcResponseMsg msg) { - return new JsonParser().parse(msg.getData()); - } - - public static JsonElement toErrorJson(String errorMsg) { - JsonObject error = new JsonObject(); - error.addProperty("error", errorMsg); - return error; - } -} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java deleted file mode 100644 index 2e7b412c31..0000000000 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ /dev/null @@ -1,78 +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.common.transport.session; - -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.msg.session.SessionContext; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.auth.DeviceAuthResult; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; - -import java.util.Optional; - -/** - * @author Andrew Shvayka - */ -@Slf4j -public abstract class DeviceAwareSessionContext implements SessionContext { - - protected final DeviceAuthService authService; - protected final SessionMsgProcessor processor; - - protected volatile Device device; - - public DeviceAwareSessionContext(SessionMsgProcessor processor, DeviceAuthService authService) { - this.processor = processor; - this.authService = authService; - } - - public DeviceAwareSessionContext(SessionMsgProcessor processor, DeviceAuthService authService, Device device) { - this(processor, authService); - this.device = device; - } - - - public boolean login(DeviceCredentialsFilter credentials) { - DeviceAuthResult result = authService.process(credentials); - if (result.isSuccess()) { - Optional deviceOpt = authService.findDeviceById(result.getDeviceId()); - if (deviceOpt.isPresent()) { - device = deviceOpt.get(); - } - return true; - } else { - log.debug("Can't find device using credentials [{}] due to {}", credentials, result.getErrorMsg()); - return false; - } - } - - public DeviceAuthService getAuthService() { - return authService; - } - - public SessionMsgProcessor getProcessor() { - return processor; - } - - public Device getDevice() { - return device; - } - - -} diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml new file mode 100644 index 0000000000..4ed6ed784d --- /dev/null +++ b/common/transport/transport-api/pom.xml @@ -0,0 +1,117 @@ + + + 4.0.0 + + org.thingsboard.common + 2.2.0-SNAPSHOT + transport + + org.thingsboard.common.transport + transport-api + jar + + Thingsboard Server Common Transport components + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common + data + + + org.thingsboard.common + message + + + org.thingsboard.common + queue + + + com.google.code.gson + gson + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + junit + junit + test + + + org.mockito + mockito-all + test + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter-web + provided + + + com.google.guava + guava + + + org.apache.commons + commons-lang3 + + + com.google.protobuf + protobuf-java + + + com.github.vladimir-bukhtoyarov + bucket4j-core + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + + + diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java new file mode 100644 index 0000000000..09d1bcb08f --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgListener.java @@ -0,0 +1,38 @@ +/** + * 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.common.transport; + +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; + +/** + * Created by ashvayka on 04.10.18. + */ +public interface SessionMsgListener { + + void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse); + + void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification); + + void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification); + + void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest); + + void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java similarity index 88% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java index 4cff643a5a..992492f7d6 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/SessionMsgProcessor.java @@ -16,12 +16,9 @@ package org.thingsboard.server.common.transport; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; public interface SessionMsgProcessor { - void process(SessionAwareMsg msg); - void onDeviceAdded(Device device); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/SessionId.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java similarity index 77% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/SessionId.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java index 1b4772768f..b59d49362b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/SessionId.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.id; +package org.thingsboard.server.common.transport; -import java.io.Serializable; +import org.thingsboard.server.common.msg.session.SessionContext; -public interface SessionId extends Serializable { - - String toUidStr(); +public interface TransportAdaptor { } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java new file mode 100644 index 0000000000..ab429829e4 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -0,0 +1,68 @@ +/** + * 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.common.transport; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; +import org.thingsboard.server.kafka.TbNodeIdProvider; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by ashvayka on 15.10.18. + */ +@Slf4j +@Data +public class TransportContext { + + protected final ObjectMapper mapper = new ObjectMapper(); + + @Autowired + private TransportService transportService; + + @Autowired + private TbNodeIdProvider nodeIdProvider; + + @Autowired(required = false) + private HostRequestsQuotaService quotaService; + + @Getter + private ExecutorService executor; + + @PostConstruct + public void init() { + executor = Executors.newCachedThreadPool(); + } + + @PreDestroy + public void stop() { + if (executor != null) { + executor.shutdownNow(); + } + } + + public String getNodeId() { + return nodeIdProvider.getNodeId(); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java new file mode 100644 index 0000000000..a47438f30d --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -0,0 +1,70 @@ +/** + * 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.common.transport; + +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; + +/** + * Created by ashvayka on 04.10.18. + */ +public interface TransportService { + + void process(ValidateDeviceTokenRequestMsg msg, + TransportServiceCallback callback); + + void process(ValidateDeviceX509CertRequestMsg msg, + TransportServiceCallback callback); + + void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg msg, + TransportServiceCallback callback); + + boolean checkLimits(SessionInfoProto sessionInfo, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback); + + void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback); + + void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); + + void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); + + void deregisterSession(SessionInfoProto sessionInfo); + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceRequestMsg.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportServiceCallback.java similarity index 76% rename from common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceRequestMsg.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportServiceCallback.java index 1ebeb671a6..5443f7ed29 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceRequestMsg.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportServiceCallback.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.session; +package org.thingsboard.server.common.transport; /** - * @author Andrew Shvayka + * Created by ashvayka on 04.10.18. */ -public interface FromDeviceRequestMsg extends FromDeviceMsg { +public interface TransportServiceCallback { - Integer getRequestId(); + void onSuccess(T msg); + void onError(Throwable e); } diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java new file mode 100644 index 0000000000..7498e8ee2a --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -0,0 +1,430 @@ +/** + * 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.common.transport.adaptor; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.kv.AttributeKey; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.msg.kv.AttributesKVMsg; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; +import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; +import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class JsonConverter { + + private static final Gson GSON = new Gson(); + private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; + private static final String DEVICE_PROPERTY = "device"; + + public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonObject) throws JsonSyntaxException { + long systemTs = System.currentTimeMillis(); + PostTelemetryMsg.Builder builder = PostTelemetryMsg.newBuilder(); + if (jsonObject.isJsonObject()) { + parseObject(builder, systemTs, jsonObject); + } else if (jsonObject.isJsonArray()) { + jsonObject.getAsJsonArray().forEach(je -> { + if (je.isJsonObject()) { + parseObject(builder, systemTs, je.getAsJsonObject()); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + je); + } + }); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonObject); + } + return builder.build(); + } + + public static PostAttributeMsg convertToAttributesProto(JsonElement jsonObject) throws JsonSyntaxException { + if (jsonObject.isJsonObject()) { + PostAttributeMsg.Builder result = PostAttributeMsg.newBuilder(); + List keyValueList = parseProtoValues(jsonObject.getAsJsonObject()); + result.addAllKv(keyValueList); + return result.build(); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonObject); + } + } + + public static JsonElement toJson(TransportProtos.ToDeviceRpcRequestMsg msg, boolean includeRequestId) { + JsonObject result = new JsonObject(); + if (includeRequestId) { + result.addProperty("id", msg.getRequestId()); + } + result.addProperty("method", msg.getMethodName()); + result.add("params", new JsonParser().parse(msg.getParams())); + return result; + } + + private static void parseObject(PostTelemetryMsg.Builder builder, long systemTs, JsonElement jsonObject) { + JsonObject jo = jsonObject.getAsJsonObject(); + if (jo.has("ts") && jo.has("values")) { + parseWithTs(builder, jo); + } else { + parseWithoutTs(builder, systemTs, jo); + } + } + + private static void parseWithoutTs(PostTelemetryMsg.Builder request, long systemTs, JsonObject jo) { + TsKvListProto.Builder builder = TsKvListProto.newBuilder(); + builder.setTs(systemTs); + builder.addAllKv(parseProtoValues(jo)); + request.addTsKvList(builder.build()); + } + + private static void parseWithTs(PostTelemetryMsg.Builder request, JsonObject jo) { + TsKvListProto.Builder builder = TsKvListProto.newBuilder(); + builder.setTs(jo.get("ts").getAsLong()); + builder.addAllKv(parseProtoValues(jo.get("values").getAsJsonObject())); + request.addTsKvList(builder.build()); + } + + private static List parseProtoValues(JsonObject valuesObject) { + List result = new ArrayList<>(); + for (Entry valueEntry : valuesObject.entrySet()) { + JsonElement element = valueEntry.getValue(); + if (element.isJsonPrimitive()) { + JsonPrimitive value = element.getAsJsonPrimitive(); + if (value.isString()) { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()).build()); + } else if (value.isBoolean()) { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) + .setBoolV(value.getAsBoolean()).build()); + } else if (value.isNumber()) { + if (value.getAsString().contains(".")) { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.DOUBLE_V) + .setDoubleV(value.getAsDouble()).build()); + } else { + try { + long longValue = Long.parseLong(value.getAsString()); + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.LONG_V) + .setLongV(longValue).build()); + } catch (NumberFormatException e) { + throw new JsonSyntaxException("Big integer values are not supported!"); + } + } + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); + } + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); + } + } + return result; + } + + public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { + JsonObject object = json.getAsJsonObject(); + return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(object.get("method").getAsString()).setParams(GSON.toJson(object.get("params"))).build(); + } + + private static void parseNumericValue(List result, Entry valueEntry, JsonPrimitive value) { + if (value.getAsString().contains(".")) { + result.add(new DoubleDataEntry(valueEntry.getKey(), value.getAsDouble())); + } else { + try { + long longValue = Long.parseLong(value.getAsString()); + result.add(new LongDataEntry(valueEntry.getKey(), longValue)); + } catch (NumberFormatException e) { + throw new JsonSyntaxException("Big integer values are not supported!"); + } + } + } + + public static JsonObject toJson(GetAttributeResponseMsg payload) { + JsonObject result = new JsonObject(); + if (payload.getClientAttributeListCount() > 0) { + JsonObject attrObject = new JsonObject(); + payload.getClientAttributeListList().forEach(addToObjectFromProto(attrObject)); + result.add("client", attrObject); + } + if (payload.getSharedAttributeListCount() > 0) { + JsonObject attrObject = new JsonObject(); + payload.getSharedAttributeListList().forEach(addToObjectFromProto(attrObject)); + result.add("shared", attrObject); + } + if (payload.getDeletedAttributeKeysCount() > 0) { + JsonArray attrObject = new JsonArray(); + payload.getDeletedAttributeKeysList().forEach(attrObject::add); + result.add("deleted", attrObject); + } + return result; + } + + public static JsonElement toJson(AttributeUpdateNotificationMsg payload) { + JsonObject result = new JsonObject(); + if (payload.getSharedUpdatedCount() > 0) { + payload.getSharedUpdatedList().forEach(addToObjectFromProto(result)); + } + if (payload.getSharedDeletedCount() > 0) { + JsonArray attrObject = new JsonArray(); + payload.getSharedDeletedList().forEach(attrObject::add); + result.add("deleted", attrObject); + } + return result; + } + + public static JsonObject toJson(AttributesKVMsg payload, boolean asMap) { + JsonObject result = new JsonObject(); + if (asMap) { + if (!payload.getClientAttributes().isEmpty()) { + JsonObject attrObject = new JsonObject(); + payload.getClientAttributes().forEach(addToObject(attrObject)); + result.add("client", attrObject); + } + if (!payload.getSharedAttributes().isEmpty()) { + JsonObject attrObject = new JsonObject(); + payload.getSharedAttributes().forEach(addToObject(attrObject)); + result.add("shared", attrObject); + } + } else { + payload.getClientAttributes().forEach(addToObject(result)); + payload.getSharedAttributes().forEach(addToObject(result)); + } + if (!payload.getDeletedAttributes().isEmpty()) { + JsonArray attrObject = new JsonArray(); + payload.getDeletedAttributes().forEach(addToObject(attrObject)); + result.add("deleted", attrObject); + } + return result; + } + + public static JsonObject getJsonObjectForGateway(TransportProtos.GetAttributeResponseMsg responseMsg) { + JsonObject result = new JsonObject(); + result.addProperty("id", responseMsg.getRequestId()); + if (responseMsg.getClientAttributeListCount() > 0) { + addValues(result, responseMsg.getClientAttributeListList()); + } + if (responseMsg.getSharedAttributeListCount() > 0) { + addValues(result, responseMsg.getSharedAttributeListList()); + } + return result; + } + + public static JsonObject getJsonObjectForGateway(String deviceName, AttributeUpdateNotificationMsg notificationMsg) { + JsonObject result = new JsonObject(); + result.addProperty(DEVICE_PROPERTY, deviceName); + result.add("data", toJson(notificationMsg)); + return result; + } + + private static void addValues(JsonObject result, List kvList) { + if (kvList.size() == 1) { + addValueToJson(result, "value", kvList.get(0).getKv()); + } else { + JsonObject values; + if (result.has("values")) { + values = result.get("values").getAsJsonObject(); + } else { + values = new JsonObject(); + result.add("values", values); + } + kvList.forEach(value -> addValueToJson(values, value.getKv().getKey(), value.getKv())); + } + } + + private static void addValueToJson(JsonObject json, String name, TransportProtos.KeyValueProto entry) { + switch (entry.getType()) { + case BOOLEAN_V: + json.addProperty(name, entry.getBoolV()); + break; + case STRING_V: + json.addProperty(name, entry.getStringV()); + break; + case DOUBLE_V: + json.addProperty(name, entry.getDoubleV()); + break; + case LONG_V: + json.addProperty(name, entry.getLongV()); + break; + } + } + + private static Consumer addToObject(JsonArray result) { + return key -> result.add(key.getAttributeKey()); + } + + private static Consumer addToObjectFromProto(JsonObject result) { + return de -> { + JsonPrimitive value; + switch (de.getKv().getType()) { + case BOOLEAN_V: + value = new JsonPrimitive(de.getKv().getBoolV()); + break; + case DOUBLE_V: + value = new JsonPrimitive(de.getKv().getDoubleV()); + break; + case LONG_V: + value = new JsonPrimitive(de.getKv().getLongV()); + break; + case STRING_V: + value = new JsonPrimitive(de.getKv().getStringV()); + break; + default: + throw new IllegalArgumentException("Unsupported data type: " + de.getKv().getType()); + } + result.add(de.getKv().getKey(), value); + }; + } + + private static Consumer addToObject(JsonObject result) { + return de -> { + JsonPrimitive value; + switch (de.getDataType()) { + case BOOLEAN: + value = new JsonPrimitive(de.getBooleanValue().get()); + break; + case DOUBLE: + value = new JsonPrimitive(de.getDoubleValue().get()); + break; + case LONG: + value = new JsonPrimitive(de.getLongValue().get()); + break; + case STRING: + value = new JsonPrimitive(de.getStrValue().get()); + break; + default: + throw new IllegalArgumentException("Unsupported data type: " + de.getDataType()); + } + result.add(de.getKey(), value); + }; + } + + public static JsonElement toJson(TransportProtos.ToServerRpcResponseMsg msg) { + if (StringUtils.isEmpty(msg.getError())) { + return new JsonParser().parse(msg.getPayload()); + } else { + JsonObject errorMsg = new JsonObject(); + errorMsg.addProperty("error", msg.getError()); + return errorMsg; + } + } + + public static JsonElement toErrorJson(String errorMsg) { + JsonObject error = new JsonObject(); + error.addProperty("error", errorMsg); + return error; + } + + public static JsonElement toGatewayJson(String deviceName, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { + JsonObject result = new JsonObject(); + result.addProperty(DEVICE_PROPERTY, deviceName); + result.add("data", JsonConverter.toJson(rpcRequest, true)); + return result; + } + + public static Set convertToAttributes(JsonElement element) { + Set result = new HashSet<>(); + long ts = System.currentTimeMillis(); + result.addAll(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); + return result; + } + + private static List parseValues(JsonObject valuesObject) { + List result = new ArrayList<>(); + for (Entry valueEntry : valuesObject.entrySet()) { + JsonElement element = valueEntry.getValue(); + if (element.isJsonPrimitive()) { + JsonPrimitive value = element.getAsJsonPrimitive(); + if (value.isString()) { + result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); + } else if (value.isBoolean()) { + result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean())); + } else if (value.isNumber()) { + parseNumericValue(result, valueEntry, value); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); + } + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); + } + } + return result; + } + + public static Map> convertToTelemetry(JsonElement jsonObject, long systemTs) throws JsonSyntaxException { + Map> result = new HashMap<>(); + if (jsonObject.isJsonObject()) { + parseObject(result, systemTs, jsonObject); + } else if (jsonObject.isJsonArray()) { + jsonObject.getAsJsonArray().forEach(je -> { + if (je.isJsonObject()) { + parseObject(result, systemTs, je.getAsJsonObject()); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + je); + } + }); + } else { + throw new JsonSyntaxException(CAN_T_PARSE_VALUE + jsonObject); + } + return result; + } + + private static void parseObject(Map> result, long systemTs, JsonElement jsonObject) { + JsonObject jo = jsonObject.getAsJsonObject(); + if (jo.has("ts") && jo.has("values")) { + parseWithTs(result, jo); + } else { + parseWithoutTs(result, systemTs, jo); + } + } + + private static void parseWithoutTs(Map> result, long systemTs, JsonObject jo) { + for (KvEntry entry : parseValues(jo)) { + result.computeIfAbsent(systemTs, tmp -> new ArrayList<>()).add(entry); + } + } + + public static void parseWithTs(Map> result, JsonObject jo) { + long ts = jo.get("ts").getAsLong(); + JsonObject valuesObject = jo.get("values").getAsJsonObject(); + for (KvEntry entry : parseValues(valuesObject)) { + result.computeIfAbsent(ts, tmp -> new ArrayList<>()).add(entry); + } + } + + +} diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthResult.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/DeviceAuthService.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/AbstractQuotaService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/AbstractQuotaService.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/AbstractQuotaService.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/AbstractQuotaService.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/Clock.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/Clock.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/Clock.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/Clock.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/QuotaService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/QuotaService.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/QuotaService.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/QuotaService.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/RequestLimitPolicy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/RequestLimitPolicy.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/RequestLimitPolicy.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/RequestLimitPolicy.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryCleaner.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryCleaner.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryCleaner.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryCleaner.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryLogger.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryLogger.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryLogger.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostIntervalRegistryLogger.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestIntervalRegistry.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestIntervalRegistry.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestIntervalRegistry.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestIntervalRegistry.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestLimitPolicy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestLimitPolicy.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestLimitPolicy.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestLimitPolicy.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestsQuotaService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestsQuotaService.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestsQuotaService.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/host/HostRequestsQuotaService.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCount.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCount.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCount.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCount.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryCleaner.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryCleaner.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryCleaner.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryCleaner.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLogger.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLogger.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLogger.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLogger.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/KeyBasedIntervalRegistry.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/KeyBasedIntervalRegistry.java similarity index 100% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/KeyBasedIntervalRegistry.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/inmemory/KeyBasedIntervalRegistry.java diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java similarity index 86% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java index c48117069a..47b427840a 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryCleaner.java @@ -16,10 +16,12 @@ package org.thingsboard.server.common.transport.quota.tenant; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryCleaner; @Component +@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false) public class TenantIntervalRegistryCleaner extends IntervalRegistryCleaner { public TenantIntervalRegistryCleaner(TenantMsgsIntervalRegistry intervalRegistry, diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java similarity index 91% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java index c56f457423..3bdcf93d07 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantIntervalRegistryLogger.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.transport.quota.tenant; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.quota.inmemory.IntervalRegistryLogger; @@ -25,6 +26,7 @@ import java.util.concurrent.TimeUnit; @Slf4j @Component +@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false) public class TenantIntervalRegistryLogger extends IntervalRegistryLogger { private final long logIntervalMin; diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java similarity index 87% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java index 6e8402c701..3402e62472 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantMsgsIntervalRegistry.java @@ -16,10 +16,12 @@ package org.thingsboard.server.common.transport.quota.tenant; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.quota.inmemory.KeyBasedIntervalRegistry; @Component +@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false) public class TenantMsgsIntervalRegistry extends KeyBasedIntervalRegistry { public TenantMsgsIntervalRegistry(@Value("${quota.rule.tenant.intervalMs}") long intervalDurationMs, diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java similarity index 87% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java index a68860a2e0..875a666a28 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantQuotaService.java @@ -16,10 +16,12 @@ package org.thingsboard.server.common.transport.quota.tenant; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.quota.AbstractQuotaService; @Component +@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false) public class TenantQuotaService extends AbstractQuotaService { public TenantQuotaService(TenantMsgsIntervalRegistry requestRegistry, TenantRequestLimitPolicy requestsPolicy, diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java similarity index 84% rename from common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java index cc32c81ad7..c5dfc40480 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/quota/tenant/TenantRequestLimitPolicy.java @@ -16,10 +16,12 @@ package org.thingsboard.server.common.transport.quota.tenant; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.quota.RequestLimitPolicy; @Component +@ConditionalOnProperty(prefix = "quota.rule.tenant", value = "enabled", havingValue = "true", matchIfMissing = false) public class TenantRequestLimitPolicy extends RequestLimitPolicy { public TenantRequestLimitPolicy(@Value("${quota.rule.tenant.limit}") long limit) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java new file mode 100644 index 0000000000..265dacb5de --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java @@ -0,0 +1,164 @@ +/** + * 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.common.transport.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Created by ashvayka on 17.10.18. + */ +@Slf4j +public abstract class AbstractTransportService implements TransportService { + + @Value("${transport.rate_limits.enabled}") + private boolean rateLimitEnabled; + @Value("${transport.rate_limits.tenant}") + private String perTenantLimitsConf; + @Value("${transport.rate_limits.tenant}") + private String perDevicesLimitsConf; + + protected ScheduledExecutorService schedulerExecutor; + protected ExecutorService transportCallbackExecutor; + private ConcurrentMap sessions = new ConcurrentHashMap<>(); + + //TODO: Implement cleanup of this maps. + private ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); + private ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); + + @Override + public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { + sessions.putIfAbsent(toId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); + //TODO: monitor sessions periodically: PING REQ/RESP, etc. + } + + @Override + public void registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { + sessions.putIfAbsent(toId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener)); + schedulerExecutor.schedule(() -> { + listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); + deregisterSession(sessionInfo); + }, timeout, TimeUnit.MILLISECONDS); + } + + @Override + public void deregisterSession(TransportProtos.SessionInfoProto sessionInfo) { + sessions.remove(toId(sessionInfo)); + } + + @Override + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, TransportServiceCallback callback) { + if (!rateLimitEnabled) { + return true; + } + TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); + TbTransportRateLimits rateLimits = perTenantLimits.computeIfAbsent(tenantId, id -> new TbTransportRateLimits(perTenantLimitsConf)); + if (!rateLimits.tryConsume()) { + if (callback != null) { + callback.onError(new TbRateLimitsException(EntityType.TENANT)); + } + return false; + } + DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); + rateLimits = perDeviceLimits.computeIfAbsent(deviceId, id -> new TbTransportRateLimits(perDevicesLimitsConf)); + if (!rateLimits.tryConsume()) { + if (callback != null) { + callback.onError(new TbRateLimitsException(EntityType.DEVICE)); + } + return false; + } + return true; + } + + protected void processToTransportMsg(TransportProtos.DeviceActorToTransportMsg toSessionMsg) { + UUID sessionId = new UUID(toSessionMsg.getSessionIdMSB(), toSessionMsg.getSessionIdLSB()); + SessionMetaData md = sessions.get(sessionId); + if (md != null) { + SessionMsgListener listener = md.getListener(); + transportCallbackExecutor.submit(() -> { + if (toSessionMsg.hasGetAttributesResponse()) { + listener.onGetAttributesResponse(toSessionMsg.getGetAttributesResponse()); + } + if (toSessionMsg.hasAttributeUpdateNotification()) { + listener.onAttributeUpdate(toSessionMsg.getAttributeUpdateNotification()); + } + if (toSessionMsg.hasSessionCloseNotification()) { + listener.onRemoteSessionCloseCommand(toSessionMsg.getSessionCloseNotification()); + } + if (toSessionMsg.hasToDeviceRequest()) { + listener.onToDeviceRpcRequest(toSessionMsg.getToDeviceRequest()); + } + if (toSessionMsg.hasToServerResponse()) { + listener.onToServerRpcResponse(toSessionMsg.getToServerResponse()); + } + }); + if (md.getSessionType() == TransportProtos.SessionType.SYNC) { + deregisterSession(md.getSessionInfo()); + } + } else { + //TODO: should we notify the device actor about missed session? + log.debug("[{}] Missing session.", sessionId); + } + } + + protected UUID toId(TransportProtos.SessionInfoProto sessionInfo) { + return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); + } + + String getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { + return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()).toString(); + } + + public void init() { + if (rateLimitEnabled) { + //Just checking the configuration parameters + new TbTransportRateLimits(perTenantLimitsConf); + new TbTransportRateLimits(perDevicesLimitsConf); + } + this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(); + this.transportCallbackExecutor = new ThreadPoolExecutor(0, 20, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); + } + + public void destroy() { + if (rateLimitEnabled) { + perTenantLimits.clear(); + perDeviceLimits.clear(); + } + if (schedulerExecutor != null) { + schedulerExecutor.shutdownNow(); + } + if (transportCallbackExecutor != null) { + transportCallbackExecutor.shutdownNow(); + } + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java new file mode 100644 index 0000000000..6774942de3 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java @@ -0,0 +1,331 @@ +/** + * 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.common.transport.service; + +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.admin.CreateTopicsResult; +import org.apache.kafka.clients.admin.NewTopic; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.producer.Callback; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.*; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.kafka.*; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Created by ashvayka on 05.10.18. + */ +@ConditionalOnExpression("'${transport.type:null}'=='null'") +@Service +@Slf4j +public class RemoteTransportService extends AbstractTransportService { + + @Value("${kafka.rule_engine.topic}") + private String ruleEngineTopic; + @Value("${kafka.notifications.topic}") + private String notificationsTopic; + @Value("${kafka.notifications.poll_interval}") + private int notificationsPollDuration; + @Value("${kafka.notifications.auto_commit_interval}") + private int notificationsAutoCommitInterval; + @Value("${kafka.transport_api.requests_topic}") + private String transportApiRequestsTopic; + @Value("${kafka.transport_api.responses_topic}") + private String transportApiResponsesTopic; + @Value("${kafka.transport_api.max_pending_requests}") + private long maxPendingRequests; + @Value("${kafka.transport_api.max_requests_timeout}") + private long maxRequestsTimeout; + @Value("${kafka.transport_api.response_poll_interval}") + private int responsePollDuration; + @Value("${kafka.transport_api.response_auto_commit_interval}") + private int autoCommitInterval; + + @Autowired + private TbKafkaSettings kafkaSettings; + //We use this to get the node id. We should replace this with a component that provides the node id. + @Autowired + private TbNodeIdProvider nodeIdProvider; + + private TbKafkaRequestTemplate transportApiTemplate; + private TBKafkaProducerTemplate ruleEngineProducer; + private TBKafkaConsumerTemplate mainConsumer; + + private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(); + + private volatile boolean stopped = false; + + @PostConstruct + public void init() { + super.init(); + + TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder requestBuilder = TBKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.defaultTopic(transportApiRequestsTopic); + requestBuilder.encoder(new TransportApiRequestEncoder()); + + TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder responseBuilder = TBKafkaConsumerTemplate.builder(); + responseBuilder.settings(kafkaSettings); + responseBuilder.topic(transportApiResponsesTopic + "." + nodeIdProvider.getNodeId()); + responseBuilder.clientId("transport-api-client-" + nodeIdProvider.getNodeId()); + responseBuilder.groupId("transport-api-client"); + responseBuilder.autoCommit(true); + responseBuilder.autoCommitIntervalMs(autoCommitInterval); + responseBuilder.decoder(new TransportApiResponseDecoder()); + + TbKafkaRequestTemplate.TbKafkaRequestTemplateBuilder + builder = TbKafkaRequestTemplate.builder(); + builder.requestTemplate(requestBuilder.build()); + builder.responseTemplate(responseBuilder.build()); + builder.maxPendingRequests(maxPendingRequests); + builder.maxRequestTimeout(maxRequestsTimeout); + builder.pollInterval(responsePollDuration); + transportApiTemplate = builder.build(); + transportApiTemplate.init(); + + TBKafkaProducerTemplate.TBKafkaProducerTemplateBuilder ruleEngineProducerBuilder = TBKafkaProducerTemplate.builder(); + ruleEngineProducerBuilder.settings(kafkaSettings); + ruleEngineProducerBuilder.defaultTopic(ruleEngineTopic); + ruleEngineProducerBuilder.encoder(new ToRuleEngineMsgEncoder()); + ruleEngineProducer = ruleEngineProducerBuilder.build(); + ruleEngineProducer.init(); + + String notificationsTopicName = notificationsTopic + "." + nodeIdProvider.getNodeId(); + + try { + TBKafkaAdmin admin = new TBKafkaAdmin(kafkaSettings); + CreateTopicsResult result = admin.createTopic(new NewTopic(notificationsTopicName, 1, (short) 1)); + result.all().get(); + } catch (Exception e) { + log.trace("Failed to create topic: {}", e.getMessage(), e); + } + + TBKafkaConsumerTemplate.TBKafkaConsumerTemplateBuilder mainConsumerBuilder = TBKafkaConsumerTemplate.builder(); + mainConsumerBuilder.settings(kafkaSettings); + mainConsumerBuilder.topic(notificationsTopicName); + mainConsumerBuilder.clientId("transport-" + nodeIdProvider.getNodeId()); + mainConsumerBuilder.groupId("transport"); + mainConsumerBuilder.autoCommit(true); + mainConsumerBuilder.autoCommitIntervalMs(notificationsAutoCommitInterval); + mainConsumerBuilder.decoder(new ToTransportMsgResponseDecoder()); + mainConsumer = mainConsumerBuilder.build(); + mainConsumer.subscribe(); + + mainConsumerExecutor.execute(() -> { + while (!stopped) { + try { + ConsumerRecords records = mainConsumer.poll(Duration.ofMillis(notificationsPollDuration)); + records.forEach(record -> { + try { + ToTransportMsg toTransportMsg = mainConsumer.decode(record); + if (toTransportMsg.hasToDeviceSessionMsg()) { + processToTransportMsg(toTransportMsg.getToDeviceSessionMsg()); + } + } catch (Throwable e) { + log.warn("Failed to process the notification.", e); + } + }); + } catch (Exception e) { + log.warn("Failed to obtain messages from queue.", e); + try { + Thread.sleep(notificationsPollDuration); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new requests", e2); + } + } + } + }); + } + + @PreDestroy + public void destroy() { + super.destroy(); + stopped = true; + if (transportApiTemplate != null) { + transportApiTemplate.stop(); + } + if (mainConsumer != null) { + mainConsumer.unsubscribe(); + } + if (mainConsumerExecutor != null) { + mainConsumerExecutor.shutdownNow(); + } + } + + @Override + public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { + AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getToken(), + TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()), + response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); + } + + @Override + public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getHash(), + TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()), + response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); + } + + @Override + public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback) { + AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getDeviceName(), + TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()), + response -> callback.onSuccess(response.getGetOrCreateDeviceResponseMsg()), callback::onError, transportCallbackExecutor); + } + + @Override + public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setSessionEvent(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setPostTelemetry(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setPostAttributes(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setGetAttributes(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setSubscribeToAttributes(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setSubscribeToRPC(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setToDeviceRPCCallResponse(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + @Override + public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, callback)) { + ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( + TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) + .setToServerRPCCallRequest(msg).build() + ).build(); + send(sessionInfo, toRuleEngineMsg, callback); + } + } + + private static class TransportCallbackAdaptor implements Callback { + private final TransportServiceCallback callback; + + TransportCallbackAdaptor(TransportServiceCallback callback) { + this.callback = callback; + } + + @Override + public void onCompletion(RecordMetadata metadata, Exception exception) { + if (exception == null) { + if (callback != null) { + callback.onSuccess(null); + } + } else { + if (callback != null) { + callback.onError(exception); + } + } + } + } + + private void send(SessionInfoProto sessionInfo, ToRuleEngineMsg toRuleEngineMsg, TransportServiceCallback callback) { + ruleEngineProducer.send(getRoutingKey(sessionInfo), toRuleEngineMsg, new TransportCallbackAdaptor(callback)); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java similarity index 60% rename from application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java index 0b6fe54c4a..1de57114e4 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java @@ -13,24 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.actors.device; +package org.thingsboard.server.common.transport.service; import lombok.Data; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.TbActorMsg; - -import java.util.UUID; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.gen.transport.TransportProtos; /** - * Created by ashvayka on 15.03.18. + * Created by ashvayka on 15.10.18. */ @Data -public final class RuleEngineQueuePutAckMsg implements TbActorMsg { +public class SessionMetaData { - private final UUID id; + private final TransportProtos.SessionInfoProto sessionInfo; + private final TransportProtos.SessionType sessionType; + private final SessionMsgListener listener; - @Override - public MsgType getMsgType() { - return MsgType.RULE_ENGINE_QUEUE_PUT_ACK_MSG; - } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbRateLimitsException.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbRateLimitsException.java new file mode 100644 index 0000000000..9d2669da99 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbRateLimitsException.java @@ -0,0 +1,29 @@ +/** + * 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.common.transport.service; + +import org.thingsboard.server.common.data.EntityType; + +/** + * Created by ashvayka on 22.10.18. + */ +public class TbRateLimitsException extends Exception { + private final EntityType entityType; + + TbRateLimitsException(EntityType entityType) { + this.entityType = entityType; + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbTransportRateLimits.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbTransportRateLimits.java new file mode 100644 index 0000000000..d598734a5a --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TbTransportRateLimits.java @@ -0,0 +1,53 @@ +/** + * 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.common.transport.service; + +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket4j; +import io.github.bucket4j.local.LocalBucket; +import io.github.bucket4j.local.LocalBucketBuilder; + +import java.time.Duration; + +/** + * Created by ashvayka on 22.10.18. + */ +class TbTransportRateLimits { + private final LocalBucket bucket; + + public TbTransportRateLimits(String limitsConfiguration) { + LocalBucketBuilder builder = Bucket4j.builder(); + boolean initialized = false; + for (String limitSrc : limitsConfiguration.split(",")) { + long capacity = Long.parseLong(limitSrc.split(":")[0]); + long duration = Long.parseLong(limitSrc.split(":")[1]); + builder.addLimit(Bandwidth.simple(capacity, Duration.ofSeconds(duration))); + initialized = true; + } + if (initialized) { + bucket = builder.build(); + } else { + throw new IllegalArgumentException("Failed to parse rate limits configuration: " + limitsConfiguration); + } + + + } + + boolean tryConsume() { + return bucket.tryConsume(1); + } + +} diff --git a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionId.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/ToRuleEngineMsgEncoder.java similarity index 62% rename from transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionId.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/ToRuleEngineMsgEncoder.java index 3211765334..2842d7ef66 100644 --- a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionId.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/ToRuleEngineMsgEncoder.java @@ -13,25 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.http.session; +package org.thingsboard.server.common.transport.service; -import org.thingsboard.server.common.data.id.SessionId; - -import java.util.UUID; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.kafka.TbKafkaEncoder; /** - * @author Andrew Shvayka + * Created by ashvayka on 05.10.18. */ -public class HttpSessionId implements SessionId { - - private final UUID id; - - public HttpSessionId() { - this.id = UUID.randomUUID(); - } - +public class ToRuleEngineMsgEncoder implements TbKafkaEncoder { @Override - public String toUidStr() { - return id.toString(); + public byte[] encode(ToRuleEngineMsg value) { + return value.toByteArray(); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/ToTransportMsgResponseDecoder.java similarity index 59% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/ToTransportMsgResponseDecoder.java index c98ad2ae26..a905f76ace 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/ToTransportMsgResponseDecoder.java @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.core; +package org.thingsboard.server.common.transport.service; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.kafka.TbKafkaDecoder; + +import java.io.IOException; /** - * @author Andrew Shvayka + * Created by ashvayka on 05.10.18. */ -public class AttributesUnsubscribeMsg implements FromDeviceMsg { +public class ToTransportMsgResponseDecoder implements TbKafkaDecoder { @Override - public SessionMsgType getMsgType() { - return SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST; + public ToTransportMsg decode(byte[] data) throws IOException { + return ToTransportMsg.parseFrom(data); } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportApiRequestEncoder.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportApiRequestEncoder.java new file mode 100644 index 0000000000..a8f27aad8f --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportApiRequestEncoder.java @@ -0,0 +1,29 @@ +/** + * 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.common.transport.service; + +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.kafka.TbKafkaEncoder; + +/** + * Created by ashvayka on 05.10.18. + */ +public class TransportApiRequestEncoder implements TbKafkaEncoder { + @Override + public byte[] encode(TransportApiRequestMsg value) { + return value.toByteArray(); + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportApiResponseDecoder.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportApiResponseDecoder.java new file mode 100644 index 0000000000..3b5c9ce579 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportApiResponseDecoder.java @@ -0,0 +1,31 @@ +/** + * 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.common.transport.service; + +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.kafka.TbKafkaDecoder; + +import java.io.IOException; + +/** + * Created by ashvayka on 05.10.18. + */ +public class TransportApiResponseDecoder implements TbKafkaDecoder { + @Override + public TransportApiResponseMsg decode(byte[] data) throws IOException { + return TransportApiResponseMsg.parseFrom(data); + } +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java new file mode 100644 index 0000000000..93c0f41ddd --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -0,0 +1,52 @@ +/** + * 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.common.transport.session; + +import lombok.Data; +import lombok.Getter; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.msg.session.SessionContext; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +@Data +public abstract class DeviceAwareSessionContext implements SessionContext { + + @Getter + protected final UUID sessionId; + @Getter + private volatile DeviceId deviceId; + @Getter + private volatile DeviceInfoProto deviceInfo; + + public DeviceId getDeviceId() { + return deviceId; + } + + public void setDeviceInfo(DeviceInfoProto deviceInfo) { + this.deviceInfo = deviceInfo; + this.deviceId = new DeviceId(new UUID(deviceInfo.getDeviceIdMSB(), deviceInfo.getDeviceIdLSB())); + } + + + public boolean isConnected() { + return deviceInfo != null; + } +} diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/transport/transport-api/src/main/proto/transport.proto new file mode 100644 index 0000000000..0e36dabbf3 --- /dev/null +++ b/common/transport/transport-api/src/main/proto/transport.proto @@ -0,0 +1,217 @@ +/** + * 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. + */ +syntax = "proto3"; +package transport; + +option java_package = "org.thingsboard.server.gen.transport"; +option java_outer_classname = "TransportProtos"; + +/** + * Data Structures; + */ +message SessionInfoProto { + string nodeId = 1; + int64 sessionIdMSB = 2; + int64 sessionIdLSB = 3; + int64 tenantIdMSB = 4; + int64 tenantIdLSB = 5; + int64 deviceIdMSB = 6; + int64 deviceIdLSB = 7; +} + +enum SessionEvent { + OPEN = 0; + CLOSED = 1; +} + +enum SessionType { + SYNC = 0; + ASYNC = 1; +} + +enum KeyValueType { + BOOLEAN_V = 0; + LONG_V = 1; + DOUBLE_V = 2; + STRING_V = 3; +} + +message KeyValueProto { + string key = 1; + KeyValueType type = 2; + bool bool_v = 3; + int64 long_v = 4; + double double_v = 5; + string string_v = 6; +} + +message TsKvProto { + int64 ts = 1; + KeyValueProto kv = 2; +} + +message TsKvListProto { + int64 ts = 1; + repeated KeyValueProto kv = 2; +} + +message DeviceInfoProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceIdMSB = 3; + int64 deviceIdLSB = 4; + string deviceName = 5; + string deviceType = 6; + string additionalInfo = 7; +} + +/** + * Messages that use Data Structures; + */ +message SessionEventMsg { + SessionType sessionType = 1; + SessionEvent event = 2; +} + +message PostTelemetryMsg { + repeated TsKvListProto tsKvList = 1; +} + +message PostAttributeMsg { + repeated KeyValueProto kv = 1; +} + +message GetAttributeRequestMsg { + int32 requestId = 1; + repeated string clientAttributeNames = 2; + repeated string sharedAttributeNames = 3; +} + +message GetAttributeResponseMsg { + int32 requestId = 1; + repeated TsKvProto clientAttributeList = 2; + repeated TsKvProto sharedAttributeList = 3; + repeated string deletedAttributeKeys = 4; + string error = 5; +} + +message AttributeUpdateNotificationMsg { + repeated TsKvProto sharedUpdated = 1; + repeated string sharedDeleted = 2; +} + +message ValidateDeviceTokenRequestMsg { + string token = 1; +} + +message ValidateDeviceX509CertRequestMsg { + string hash = 1; +} + +message ValidateDeviceCredentialsResponseMsg { + DeviceInfoProto deviceInfo = 1; + string credentialsBody = 2; +} + +message GetOrCreateDeviceFromGatewayRequestMsg { + int64 gatewayIdMSB = 1; + int64 gatewayIdLSB = 2; + string deviceName = 3; + string deviceType = 4; +} + +message GetOrCreateDeviceFromGatewayResponseMsg { + DeviceInfoProto deviceInfo = 1; +} + +message SessionCloseNotificationProto { + string message = 1; +} + +message SubscribeToAttributeUpdatesMsg { + bool unsubscribe = 1; +} + +message SubscribeToRPCMsg { + bool unsubscribe = 1; +} + +message ToDeviceRpcRequestMsg { + int32 requestId = 1; + string methodName = 2; + string params = 3; +} + +message ToDeviceRpcResponseMsg { + int32 requestId = 1; + string payload = 2; +} + +message ToServerRpcRequestMsg { + int32 requestId = 1; + string methodName = 2; + string params = 3; +} + +message ToServerRpcResponseMsg { + int32 requestId = 1; + string payload = 2; + string error = 3; +} + +message TransportToDeviceActorMsg { + SessionInfoProto sessionInfo = 1; + SessionEventMsg sessionEvent = 2; + PostTelemetryMsg postTelemetry = 3; + PostAttributeMsg postAttributes = 4; + GetAttributeRequestMsg getAttributes = 5; + SubscribeToAttributeUpdatesMsg subscribeToAttributes = 6; + SubscribeToRPCMsg subscribeToRPC = 7; + ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 8; + ToServerRpcRequestMsg toServerRPCCallRequest = 9; +} + +message DeviceActorToTransportMsg { + int64 sessionIdMSB = 1; + int64 sessionIdLSB = 2; + SessionCloseNotificationProto sessionCloseNotification = 3; + GetAttributeResponseMsg getAttributesResponse = 4; + AttributeUpdateNotificationMsg attributeUpdateNotification = 5; + ToDeviceRpcRequestMsg toDeviceRequest = 6; + ToServerRpcResponseMsg toServerResponse = 7; +} + +/** + * Main messages; + */ +message ToRuleEngineMsg { + TransportToDeviceActorMsg toDeviceActorMsg = 1; +} + +message ToTransportMsg { + DeviceActorToTransportMsg toDeviceSessionMsg = 1; +} + +message TransportApiRequestMsg { + ValidateDeviceTokenRequestMsg validateTokenRequestMsg = 1; + ValidateDeviceX509CertRequestMsg validateX509CertRequestMsg = 2; + GetOrCreateDeviceFromGatewayRequestMsg getOrCreateDeviceRequestMsg = 3; +} + +message TransportApiResponseMsg { + ValidateDeviceCredentialsResponseMsg validateTokenResponseMsg = 1; + GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; +} \ No newline at end of file diff --git a/common/transport/src/test/java/org/thingsboard/server/common/transport/quota/ClockTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/ClockTest.java similarity index 100% rename from common/transport/src/test/java/org/thingsboard/server/common/transport/quota/ClockTest.java rename to common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/ClockTest.java diff --git a/common/transport/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestLimitPolicyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestLimitPolicyTest.java similarity index 100% rename from common/transport/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestLimitPolicyTest.java rename to common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestLimitPolicyTest.java diff --git a/common/transport/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java similarity index 86% rename from common/transport/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java rename to common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java index 20f8a55d0d..37441e9d41 100644 --- a/common/transport/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/HostRequestsQuotaServiceTest.java @@ -17,7 +17,11 @@ package org.thingsboard.server.common.transport.quota; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.transport.quota.host.*; +import org.thingsboard.server.common.transport.quota.host.HostIntervalRegistryCleaner; +import org.thingsboard.server.common.transport.quota.host.HostIntervalRegistryLogger; +import org.thingsboard.server.common.transport.quota.host.HostRequestIntervalRegistry; +import org.thingsboard.server.common.transport.quota.host.HostRequestLimitPolicy; +import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/common/transport/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistryTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistryTest.java similarity index 100% rename from common/transport/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistryTest.java rename to common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/HostRequestIntervalRegistryTest.java diff --git a/common/transport/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCountTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCountTest.java similarity index 100% rename from common/transport/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCountTest.java rename to common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalCountTest.java diff --git a/common/transport/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLoggerTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLoggerTest.java similarity index 100% rename from common/transport/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLoggerTest.java rename to common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/quota/inmemory/IntervalRegistryLoggerTest.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java index 688dfb3f9f..fc2868e805 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.RemovalCause; import com.github.benmanes.caffeine.cache.Ticker; +import com.github.benmanes.caffeine.cache.Weigher; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -31,6 +32,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -64,8 +66,9 @@ public class CaffeineCacheConfiguration { private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) { final Caffeine caffeineBuilder = Caffeine.newBuilder() + .weigher(collectionSafeWeigher()) + .maximumWeight(cacheSpec.getMaxSize()) .expireAfterWrite(cacheSpec.getTimeToLiveInMinutes(), TimeUnit.MINUTES) - .maximumSize(cacheSpec.getMaxSize()) .ticker(ticker()); return new CaffeineCache(name, caffeineBuilder.build()); } @@ -80,4 +83,12 @@ public class CaffeineCacheConfiguration { return new PreviousDeviceCredentialsIdKeyGenerator(); } + private Weigher collectionSafeWeigher() { + return (Weigher) (key, value) -> { + if(value instanceof Collection) { + return ((Collection) value).size(); + } + return 1; + }; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 4219b068f1..4b25f2b38a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.dao.EncryptionUtil; +import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 1b67ca0486..33a26997ca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; @@ -46,6 +47,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe @Autowired private DeviceService deviceService; + @Autowired + private EntityViewService entityViewService; + @Autowired private TenantService tenantService; @@ -81,6 +85,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe case DEVICE: hasName = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId())); break; + case ENTITY_VIEW: + hasName = entityViewService.findEntityViewByIdAsync(new EntityViewId(entityId.getId())); + break; case TENANT: hasName = tenantService.findTenantByIdAsync(new TenantId(entityId.getId())); break; diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java index fe1a8d1f8d..6ddc18d463 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java @@ -84,8 +84,7 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao>> futures = new ArrayList<>(); - if (savedEntityView.getKeys() != null) { - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs())); - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs())); - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh())); - } - for (ListenableFuture> future : futures) { - try { - future.get(); - } catch (InterruptedException | ExecutionException e) { - log.error("Failed to copy attributes to entity view", e); - throw new RuntimeException("Failed to copy attributes to entity view", e); - } - } return savedEntityView; } + @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public EntityView assignEntityViewToCustomer(EntityViewId entityViewId, CustomerId customerId) { EntityView entityView = findEntityViewById(entityViewId); @@ -294,36 +277,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti }); } - private ListenableFuture> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection keys) { - if (keys != null && !keys.isEmpty()) { - ListenableFuture> getAttrFuture = attributesService.find(entityView.getEntityId(), scope, keys); - return Futures.transform(getAttrFuture, attributeKvEntries -> { - List filteredAttributes = new ArrayList<>(); - if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { - filteredAttributes = - attributeKvEntries.stream() - .filter(attributeKvEntry -> { - long startTime = entityView.getStartTimeMs(); - long endTime = entityView.getEndTimeMs(); - long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); - return startTime == 0 && endTime == 0 || - (endTime == 0 && startTime < lastUpdateTs) || - (startTime == 0 && endTime > lastUpdateTs) - ? true : startTime < lastUpdateTs && endTime > lastUpdateTs; - }).collect(Collectors.toList()); - } - try { - return attributesService.save(entityView.getId(), scope, filteredAttributes).get(); - } catch (InterruptedException | ExecutionException e) { - log.error("Failed to copy attributes to entity view", e); - throw new RuntimeException("Failed to copy attributes to entity view", e); - } - }); - } else { - return Futures.immediateFuture(null); - } - } - private DataValidator entityViewValidator = new DataValidator() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java deleted file mode 100644 index 19021eb95c..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java +++ /dev/null @@ -1,34 +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.dao.queue; - -import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.TbMsg; - -import java.util.UUID; - -public interface MsgQueue { - - ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); - - ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition); - - Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition); - - ListenableFuture cleanUp(TenantId tenantId); - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java deleted file mode 100644 index ca61a63a4c..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java +++ /dev/null @@ -1,152 +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.dao.queue; - -import com.datastax.driver.core.Cluster; -import com.datastax.driver.core.HostDistance; -import com.datastax.driver.core.PoolingOptions; -import com.datastax.driver.core.Session; -import com.datastax.driver.core.utils.UUIDs; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.context.annotation.Bean; -import org.thingsboard.server.common.data.id.EntityId; -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.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; - -import javax.annotation.Nullable; -import java.net.InetSocketAddress; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -//@SpringBootApplication -//@EnableAutoConfiguration -//@ComponentScan({"org.thingsboard.rule.engine"}) -//@PropertySource("classpath:processing-pipeline.properties") -@Slf4j -public class QueueBenchmark implements CommandLineRunner { - - public static void main(String[] args) { - try { - SpringApplication.run(QueueBenchmark.class, args); - } catch (Throwable th) { - th.printStackTrace(); - System.exit(0); - } - } - - @Autowired - private MsgQueue msgQueue; - - @Override - public void run(String... strings) throws Exception { - System.out.println("It works + " + msgQueue); - - - long start = System.currentTimeMillis(); - int msgCount = 10000000; - AtomicLong count = new AtomicLong(0); - ExecutorService service = Executors.newFixedThreadPool(100); - - CountDownLatch latch = new CountDownLatch(msgCount); - for (int i = 0; i < msgCount; i++) { - service.submit(() -> { - boolean isFinished = false; - while (!isFinished) { - try { - TbMsg msg = randomMsg(); - UUID nodeId = UUIDs.timeBased(); - ListenableFuture put = msgQueue.put(new TenantId(EntityId.NULL_UUID), msg, nodeId, 100L); -// ListenableFuture put = msgQueue.ack(msg, nodeId, 100L); - Futures.addCallback(put, new FutureCallback() { - @Override - public void onSuccess(@Nullable Void result) { - latch.countDown(); - } - - @Override - public void onFailure(Throwable t) { -// t.printStackTrace(); - System.out.println("onFailure, because:" + t.getMessage()); - latch.countDown(); - } - }); - isFinished = true; - } catch (Throwable th) { -// th.printStackTrace(); - System.out.println("Repeat query, because:" + th.getMessage()); -// latch.countDown(); - } - } - }); - } - - long prev = 0L; - while (latch.getCount() != 0) { - TimeUnit.SECONDS.sleep(1); - long curr = latch.getCount(); - long rps = prev - curr; - prev = curr; - System.out.println("rps = " + rps); - } - - long end = System.currentTimeMillis(); - System.out.println("final rps = " + (msgCount / (end - start) * 1000)); - - System.out.println("Finished"); - - } - - private TbMsg randomMsg() { - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("key", "value"); - String dataStr = "someContent"; - return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr, new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L); - } - - @Bean - public Session session() { - Cluster thingsboard = Cluster.builder() - .addContactPointsWithPorts(new InetSocketAddress("127.0.0.1", 9042)) - .withClusterName("thingsboard") -// .withSocketOptions(socketOpts.getOpts()) - .withPoolingOptions(new PoolingOptions() - .setMaxRequestsPerConnection(HostDistance.LOCAL, 32768) - .setMaxRequestsPerConnection(HostDistance.REMOTE, 32768)).build(); - - Session session = thingsboard.connect("thingsboard"); - return session; - } - - @Bean - public int defaultTtl() { - return 6000; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java deleted file mode 100644 index 93057784f6..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/memory/InMemoryMsgQueue.java +++ /dev/null @@ -1,123 +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.dao.queue.memory; - -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.queue.MsgQueue; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; - -/** - * Created by ashvayka on 27.04.18. - */ -@Component -@ConditionalOnProperty(prefix = "actors.rule.queue", value = "type", havingValue = "memory", matchIfMissing = true) -@Slf4j -public class InMemoryMsgQueue implements MsgQueue { - - private ListeningExecutorService queueExecutor; - private Map>> data = new HashMap<>(); - - @PostConstruct - public void init() { - // Should be always single threaded due to absence of locks. - queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); - } - - @PreDestroy - public void stop() { - if (queueExecutor != null) { - queueExecutor.shutdownNow(); - } - } - - @Override - public ListenableFuture put(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { - return queueExecutor.submit(() -> { - data.computeIfAbsent(tenantId, key -> new HashMap<>()). - computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); - return null; - }); - } - - @Override - public ListenableFuture ack(TenantId tenantId, TbMsg msg, UUID nodeId, long clusterPartition) { - return queueExecutor.submit(() -> { - Map> tenantMap = data.get(tenantId); - if (tenantMap != null) { - InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); - Map map = tenantMap.get(key); - if (map != null) { - map.remove(msg.getId()); - if (map.isEmpty()) { - tenantMap.remove(key); - } - } - if (tenantMap.isEmpty()) { - data.remove(tenantId); - } - } - return null; - }); - } - - @Override - public Iterable findUnprocessed(TenantId tenantId, UUID nodeId, long clusterPartition) { - ListenableFuture> list = queueExecutor.submit(() -> { - Map> tenantMap = data.get(tenantId); - if (tenantMap != null) { - InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); - Map map = tenantMap.get(key); - if (map != null) { - return new ArrayList<>(map.values()); - } else { - return Collections.emptyList(); - } - } else { - return Collections.emptyList(); - } - }); - try { - return list.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - @Override - public ListenableFuture cleanUp(TenantId tenantId) { - return queueExecutor.submit(() -> { - data.remove(tenantId); - return null; - }); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 4a09bb7a23..4658cbe490 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -79,12 +79,16 @@ public class BaseTimeseriesService implements TimeseriesService { if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); List filteredKeys = new ArrayList<>(keys); - if (!entityView.getKeys().getTimeseries().isEmpty()) { + if (entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null && + !entityView.getKeys().getTimeseries().isEmpty()) { filteredKeys.retainAll(entityView.getKeys().getTimeseries()); } List queries = filteredKeys.stream() - .map(key -> new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), entityView.getEndTimeMs(), 1, "ASC")) + .map(key -> { + long endTs = entityView.getEndTimeMs() != 0 ? entityView.getEndTimeMs() : Long.MAX_VALUE; + return new BaseReadTsKvQuery(key, entityView.getStartTimeMs(), endTs, 1, "DESC"); + }) .collect(Collectors.toList()); if (queries.size() > 0) { @@ -100,7 +104,17 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> findAllLatest(EntityId entityId) { validate(entityId); - return timeseriesDao.findAllLatest(entityId); + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { + EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); + if (entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null && + !entityView.getKeys().getTimeseries().isEmpty()) { + return findLatest(entityId, entityView.getKeys().getTimeseries()); + } else { + return Futures.immediateFuture(new ArrayList<>()); + } + } else { + return timeseriesDao.findAllLatest(entityId); + } } @Override diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 1e1f15daff..f49c357caa 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -45,6 +45,7 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -88,6 +89,9 @@ public abstract class AbstractServiceTest { @Autowired protected AssetService assetService; + @Autowired + protected EntityViewService entityViewService; + @Autowired protected DeviceCredentialsService deviceCredentialsService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index 4528d9afb1..88f4d84eb6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -17,9 +17,15 @@ package org.thingsboard.server.dao.service.timeseries; import com.datastax.driver.core.utils.UUIDs; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Tenant; 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.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; @@ -30,6 +36,7 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.objects.TelemetryEntityView; import org.thingsboard.server.dao.service.AbstractServiceTest; import java.util.ArrayList; @@ -61,6 +68,22 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { KvEntry doubleKvEntry = new DoubleDataEntry(DOUBLE_KEY, Double.MAX_VALUE); KvEntry booleanKvEntry = new BooleanDataEntry(BOOLEAN_KEY, Boolean.TRUE); + private TenantId tenantId; + + @Before + public void before() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + Assert.assertNotNull(savedTenant); + tenantId = savedTenant.getId(); + } + + @After + public void after() { + tenantService.deleteTenant(tenantId); + } + @Test public void testFindAllLatest() throws Exception { DeviceId deviceId = new DeviceId(UUIDs.timeBased()); @@ -69,7 +92,15 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { saveEntries(deviceId, TS - 1); saveEntries(deviceId, TS); - List tsList = tsService.findAllLatest(deviceId).get(); + testLatestTsAndVerify(deviceId); + + EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY, DOUBLE_KEY, LONG_KEY, BOOLEAN_KEY)); + + testLatestTsAndVerify(entityView.getId()); + } + + private void testLatestTsAndVerify(EntityId entityId) throws ExecutionException, InterruptedException { + List tsList = tsService.findAllLatest(entityId).get(); assertNotNull(tsList); assertEquals(4, tsList.size()); @@ -89,6 +120,18 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { assertEquals(expected, tsList); } + private EntityView saveAndCreateEntityView(DeviceId deviceId, List timeseries) { + EntityView entityView = new EntityView(); + entityView.setName("entity_view_name"); + entityView.setType("default"); + entityView.setTenantId(tenantId); + TelemetryEntityView keys = new TelemetryEntityView(); + keys.setTimeseries(timeseries); + entityView.setKeys(keys); + entityView.setEntityId(deviceId); + return entityViewService.saveEntityView(entityView); + } + @Test public void testFindLatest() throws Exception { DeviceId deviceId = new DeviceId(UUIDs.timeBased()); @@ -100,6 +143,12 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { List entries = tsService.findLatest(deviceId, Collections.singleton(STRING_KEY)).get(); Assert.assertEquals(1, entries.size()); Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0)); + + EntityView entityView = saveAndCreateEntityView(deviceId, Arrays.asList(STRING_KEY)); + + entries = tsService.findLatest(entityView.getId(), Collections.singleton(STRING_KEY)).get(); + Assert.assertEquals(1, entries.size()); + Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0)); } @Test diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties index 06a92faa01..7c3ec51e8a 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -1,7 +1,2 @@ database.entities.type=cassandra database.ts.type=cassandra - -cassandra.queue.partitioning=HOURS -cassandra.queue.ack.ttl=3600 -cassandra.queue.msg.ttl=3600 -cassandra.queue.partitions.ttl=3600 \ No newline at end of file diff --git a/msa/docker/.env b/msa/docker/.env index 111ff1e2b4..3d8faeb355 100644 --- a/msa/docker/.env +++ b/msa/docker/.env @@ -4,7 +4,10 @@ DOCKER_REPO=local-maven-build JS_EXECUTOR_DOCKER_NAME=tb-js-executor TB_NODE_DOCKER_NAME=tb-node WEB_UI_DOCKER_NAME=tb-web-ui +MQTT_TRANSPORT_DOCKER_NAME=tb-mqtt-transport +HTTP_TRANSPORT_DOCKER_NAME=tb-http-transport +COAP_TRANSPORT_DOCKER_NAME=tb-coap-transport TB_VERSION=2.2.0-SNAPSHOT -KAFKA_TOPICS=js.eval.requests:100:1 +KAFKA_TOPICS=js.eval.requests:100:1:delete --config=retention.ms=60000 --config=retention.bytes=1073741824,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=retention.bytes=1073741824,tb.rule-engine:30:1 diff --git a/msa/docker/docker-compose.yml b/msa/docker/docker-compose.yml index 93c2ab08cd..e3b56ae425 100644 --- a/msa/docker/docker-compose.yml +++ b/msa/docker/docker-compose.yml @@ -36,6 +36,9 @@ services: KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE KAFKA_CREATE_TOPICS: "${KAFKA_TOPICS}" KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false' + KAFKA_LOG_RETENTION_BYTES: 1073741824 + KAFKA_LOG_RETENTION_MS: 300000 + KAFKA_LOG_CLEANUP_POLICY: delete depends_on: - zookeeper tb-js-executor: @@ -53,8 +56,6 @@ services: image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}" ports: - "8080" - - "1883:1883" - - "5683:5683/udp" logging: driver: "json-file" options: @@ -72,6 +73,61 @@ services: - ./tb-node/log:/var/log/thingsboard depends_on: - kafka + tb-mqtt-transport1: + restart: always + image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" + ports: + - "1883" + environment: + TB_KAFKA_SERVERS: kafka:9092 + env_file: + - tb-mqtt-transport.env + depends_on: + - kafka + tb-mqtt-transport2: + restart: always + image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" + ports: + - "1883" + environment: + TB_KAFKA_SERVERS: kafka:9092 + env_file: + - tb-mqtt-transport.env + depends_on: + - kafka + tb-http-transport1: + restart: always + image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" + ports: + - "8081" + environment: + TB_KAFKA_SERVERS: kafka:9092 + env_file: + - tb-http-transport.env + depends_on: + - kafka + tb-http-transport2: + restart: always + image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" + ports: + - "8081" + environment: + TB_KAFKA_SERVERS: kafka:9092 + env_file: + - tb-http-transport.env + depends_on: + - kafka + tb-coap-transport: + restart: always + image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" + ports: + - "5683:5683/udp" + environment: + TB_KAFKA_SERVERS: kafka:9092 + env_file: + - tb-coap-transport.env + depends_on: + - kafka tb-web-ui1: restart: always image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}" @@ -103,12 +159,18 @@ services: ports: - "80:80" - "443:443" + - "1883:1883" - "9999:9999" cap_add: - NET_ADMIN environment: HTTP_PORT: 80 HTTPS_PORT: 443 + MQTT_PORT: 1883 links: - tb-web-ui1 - tb-web-ui2 + - tb-mqtt-transport1 + - tb-mqtt-transport2 + - tb-http-transport1 + - tb-http-transport2 diff --git a/msa/docker/haproxy/config/haproxy.cfg b/msa/docker/haproxy/config/haproxy.cfg index 5f174653f9..cb70552a73 100644 --- a/msa/docker/haproxy/config/haproxy.cfg +++ b/msa/docker/haproxy/config/haproxy.cfg @@ -1,6 +1,9 @@ #HA Proxy Config global - maxconn 4096 + ulimit-n 500000 + maxconn 99999 + maxpipes 99999 + tune.maxaccept 500 log 127.0.0.1 local0 log 127.0.0.1 local1 notice @@ -13,8 +16,6 @@ global defaults - option forwardfor - log global mode http @@ -30,23 +31,42 @@ listen stats stats uri /stats stats auth admin:admin@123 +listen mqtt-in + bind *:${MQTT_PORT} + mode tcp + option clitcpka # For TCP keep-alive + timeout client 3h + timeout server 3h + option tcplog + balance leastconn + server tbMqtt1 tb-mqtt-transport1:1883 check + server tbMqtt2 tb-mqtt-transport2:1883 check + frontend http-in bind *:${HTTP_PORT} + option forwardfor + reqadd X-Forwarded-Proto:\ http acl transport_http_acl path_beg /api/v1/ acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/ redirect scheme https if !letsencrypt_http_acl !transport_http_acl use_backend letsencrypt_http if letsencrypt_http_acl + use_backend tb-http-backend if transport_http_acl default_backend tb-web-backend frontend https_in bind *:${HTTPS_PORT} ssl crt /usr/local/etc/haproxy/default.pem crt /usr/local/etc/haproxy/certs.d ciphers ECDHE-RSA-AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM + option forwardfor + reqadd X-Forwarded-Proto:\ https + acl transport_http_acl path_beg /api/v1/ + use_backend tb-http-backend if transport_http_acl + default_backend tb-web-backend backend letsencrypt_http @@ -59,3 +79,10 @@ backend tb-web-backend server tbWeb1 tb-web-ui1:8080 check server tbWeb2 tb-web-ui2:8080 check http-request set-header X-Forwarded-Port %[dst_port] + +backend tb-http-backend + balance leastconn + option tcp-check + option log-health-checks + server tbHttp1 tb-http-transport1:8081 check + server tbHttp2 tb-http-transport2:8081 check diff --git a/msa/docker/tb-coap-transport.env b/msa/docker/tb-coap-transport.env new file mode 100644 index 0000000000..b3331fe849 --- /dev/null +++ b/msa/docker/tb-coap-transport.env @@ -0,0 +1,6 @@ + +COAP_BIND_ADDRESS=0.0.0.0 +COAP_BIND_PORT=5683 +COAP_TIMEOUT=10000 + +TB_KAFKA_SERVERS=localhost:9092 \ No newline at end of file diff --git a/msa/docker/tb-http-transport.env b/msa/docker/tb-http-transport.env new file mode 100644 index 0000000000..33c479104e --- /dev/null +++ b/msa/docker/tb-http-transport.env @@ -0,0 +1,6 @@ + +HTTP_BIND_ADDRESS=0.0.0.0 +HTTP_BIND_PORT=8081 +HTTP_REQUEST_TIMEOUT=60000 + +TB_KAFKA_SERVERS=localhost:9092 \ No newline at end of file diff --git a/msa/docker/tb-mqtt-transport.env b/msa/docker/tb-mqtt-transport.env new file mode 100644 index 0000000000..5ada3f786a --- /dev/null +++ b/msa/docker/tb-mqtt-transport.env @@ -0,0 +1,6 @@ + +MQTT_BIND_ADDRESS=0.0.0.0 +MQTT_BIND_PORT=1883 +MQTT_TIMEOUT=10000 + +TB_KAFKA_SERVERS=localhost:9092 \ No newline at end of file diff --git a/msa/docker/tb-node.env b/msa/docker/tb-node.env index 874bf75d55..abeccb12eb 100644 --- a/msa/docker/tb-node.env +++ b/msa/docker/tb-node.env @@ -1,8 +1,6 @@ # ThingsBoard server configuration -MQTT_BIND_ADDRESS=0.0.0.0 -MQTT_BIND_PORT=1883 -COAP_BIND_ADDRESS=0.0.0.0 -COAP_BIND_PORT=5683 + +TRANSPORT_TYPE=remote # type of database to use: sql[DEFAULT] or cassandra DATABASE_TS_TYPE=sql diff --git a/msa/pom.xml b/msa/pom.xml index dd4c3653ba..d357677a7e 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -40,6 +40,7 @@ js-executor web-ui tb-node + transport integration-tests diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile new file mode 100644 index 0000000000..dcaef3a358 --- /dev/null +++ b/msa/transport/coap/docker/Dockerfile @@ -0,0 +1,31 @@ +# +# 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. +# + +FROM openjdk:8-jre + +COPY logback.xml ${pkg.name}.conf start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ + +RUN chmod a+x /tmp/*.sh \ + && mv /tmp/start-tb-coap-transport.sh /usr/bin + +RUN dpkg -i /tmp/${pkg.name}.deb + +RUN update-rc.d ${pkg.name} disable + +RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \ + && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf + +CMD ["start-tb-coap-transport.sh"] diff --git a/msa/transport/coap/docker/logback.xml b/msa/transport/coap/docker/logback.xml new file mode 100644 index 0000000000..e9d86928bd --- /dev/null +++ b/msa/transport/coap/docker/logback.xml @@ -0,0 +1,50 @@ + + + + + + + /var/log/${pkg.name}/${pkg.name}.log + + /var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/msa/transport/coap/docker/start-tb-coap-transport.sh b/msa/transport/coap/docker/start-tb-coap-transport.sh new file mode 100755 index 0000000000..43d4602230 --- /dev/null +++ b/msa/transport/coap/docker/start-tb-coap-transport.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# 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. +# + +CONF_FOLDER="${pkg.installFolder}/conf" +jarfile=${pkg.installFolder}/bin/${pkg.name}.jar +configfile=${pkg.name}.conf + +source "${CONF_FOLDER}/${configfile}" + +echo "Starting '${project.name}' ..." + +exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.coap.ThingsboardCoapTransportApplication \ + -Dspring.jpa.hibernate.ddl-auto=none \ + -Dlogging.config=${CONF_FOLDER}/logback.xml \ + org.springframework.boot.loader.PropertiesLauncher diff --git a/msa/transport/coap/docker/tb-coap-transport.conf b/msa/transport/coap/docker/tb-coap-transport.conf new file mode 100644 index 0000000000..6d4c54aad1 --- /dev/null +++ b/msa/transport/coap/docker/tb-coap-transport.conf @@ -0,0 +1,23 @@ +# +# 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" +export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" +export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml new file mode 100644 index 0000000000..07c4e63342 --- /dev/null +++ b/msa/transport/coap/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + org.thingsboard.msa + 2.2.0-SNAPSHOT + transport + + org.thingsboard.msa.transport + coap + pom + + ThingsBoard COAP Transport Microservice + https://thingsboard.io + ThingsBoard COAP Transport Microservice + + + UTF-8 + ${basedir}/../../.. + tb-coap-transport + tb-coap-transport + thingsboard + /var/log/${pkg.name} + /usr/share/${pkg.name} + + + + + org.thingsboard.transport + coap + ${project.version} + deb + deb + provided + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-tb-coap-transport-deb + package + + copy + + + + + org.thingsboard.transport + coap + deb + deb + ${pkg.name}.deb + ${project.build.directory} + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-docker-config + process-resources + + copy-resources + + + ${project.build.directory} + + + docker + true + + + + + + + + com.spotify + dockerfile-maven-plugin + + + build-docker-image + pre-integration-test + + build + + + + + ${dockerfile.skip} + ${docker.repo}/${docker.name} + ${project.version} + true + false + ${project.build.directory} + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + + diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile new file mode 100644 index 0000000000..212047fa42 --- /dev/null +++ b/msa/transport/http/docker/Dockerfile @@ -0,0 +1,31 @@ +# +# 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. +# + +FROM openjdk:8-jre + +COPY logback.xml ${pkg.name}.conf start-tb-http-transport.sh ${pkg.name}.deb /tmp/ + +RUN chmod a+x /tmp/*.sh \ + && mv /tmp/start-tb-http-transport.sh /usr/bin + +RUN dpkg -i /tmp/${pkg.name}.deb + +RUN update-rc.d ${pkg.name} disable + +RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \ + && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf + +CMD ["start-tb-http-transport.sh"] diff --git a/msa/transport/http/docker/logback.xml b/msa/transport/http/docker/logback.xml new file mode 100644 index 0000000000..e9d86928bd --- /dev/null +++ b/msa/transport/http/docker/logback.xml @@ -0,0 +1,50 @@ + + + + + + + /var/log/${pkg.name}/${pkg.name}.log + + /var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/msa/transport/http/docker/start-tb-http-transport.sh b/msa/transport/http/docker/start-tb-http-transport.sh new file mode 100755 index 0000000000..667988fa42 --- /dev/null +++ b/msa/transport/http/docker/start-tb-http-transport.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# 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. +# + +CONF_FOLDER="${pkg.installFolder}/conf" +jarfile=${pkg.installFolder}/bin/${pkg.name}.jar +configfile=${pkg.name}.conf + +source "${CONF_FOLDER}/${configfile}" + +echo "Starting '${project.name}' ..." + +exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.http.ThingsboardHttpTransportApplication \ + -Dspring.jpa.hibernate.ddl-auto=none \ + -Dlogging.config=${CONF_FOLDER}/logback.xml \ + org.springframework.boot.loader.PropertiesLauncher diff --git a/msa/transport/http/docker/tb-http-transport.conf b/msa/transport/http/docker/tb-http-transport.conf new file mode 100644 index 0000000000..6d4c54aad1 --- /dev/null +++ b/msa/transport/http/docker/tb-http-transport.conf @@ -0,0 +1,23 @@ +# +# 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" +export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" +export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml new file mode 100644 index 0000000000..22eb299dd5 --- /dev/null +++ b/msa/transport/http/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + org.thingsboard.msa + 2.2.0-SNAPSHOT + transport + + org.thingsboard.msa.transport + http + pom + + ThingsBoard HTTP Transport Microservice + https://thingsboard.io + ThingsBoard HTTP Transport Microservice + + + UTF-8 + ${basedir}/../../.. + tb-http-transport + tb-http-transport + thingsboard + /var/log/${pkg.name} + /usr/share/${pkg.name} + + + + + org.thingsboard.transport + http + ${project.version} + deb + deb + provided + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-tb-http-transport-deb + package + + copy + + + + + org.thingsboard.transport + http + deb + deb + ${pkg.name}.deb + ${project.build.directory} + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-docker-config + process-resources + + copy-resources + + + ${project.build.directory} + + + docker + true + + + + + + + + com.spotify + dockerfile-maven-plugin + + + build-docker-image + pre-integration-test + + build + + + + + ${dockerfile.skip} + ${docker.repo}/${docker.name} + ${project.version} + true + false + ${project.build.directory} + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + + diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile new file mode 100644 index 0000000000..5827c65229 --- /dev/null +++ b/msa/transport/mqtt/docker/Dockerfile @@ -0,0 +1,31 @@ +# +# 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. +# + +FROM openjdk:8-jre + +COPY logback.xml ${pkg.name}.conf start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ + +RUN chmod a+x /tmp/*.sh \ + && mv /tmp/start-tb-mqtt-transport.sh /usr/bin + +RUN dpkg -i /tmp/${pkg.name}.deb + +RUN update-rc.d ${pkg.name} disable + +RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \ + && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf + +CMD ["start-tb-mqtt-transport.sh"] diff --git a/msa/transport/mqtt/docker/logback.xml b/msa/transport/mqtt/docker/logback.xml new file mode 100644 index 0000000000..e9d86928bd --- /dev/null +++ b/msa/transport/mqtt/docker/logback.xml @@ -0,0 +1,50 @@ + + + + + + + /var/log/${pkg.name}/${pkg.name}.log + + /var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh new file mode 100755 index 0000000000..8fe06d272e --- /dev/null +++ b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# +# 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. +# + +CONF_FOLDER="${pkg.installFolder}/conf" +jarfile=${pkg.installFolder}/bin/${pkg.name}.jar +configfile=${pkg.name}.conf + +source "${CONF_FOLDER}/${configfile}" + +echo "Starting '${project.name}' ..." + +exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication \ + -Dspring.jpa.hibernate.ddl-auto=none \ + -Dlogging.config=${CONF_FOLDER}/logback.xml \ + org.springframework.boot.loader.PropertiesLauncher diff --git a/msa/transport/mqtt/docker/tb-mqtt-transport.conf b/msa/transport/mqtt/docker/tb-mqtt-transport.conf new file mode 100644 index 0000000000..6d4c54aad1 --- /dev/null +++ b/msa/transport/mqtt/docker/tb-mqtt-transport.conf @@ -0,0 +1,23 @@ +# +# 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" +export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" +export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml new file mode 100644 index 0000000000..975f9fcdd0 --- /dev/null +++ b/msa/transport/mqtt/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + org.thingsboard.msa + 2.2.0-SNAPSHOT + transport + + org.thingsboard.msa.transport + mqtt + pom + + ThingsBoard MQTT Transport Microservice + https://thingsboard.io + ThingsBoard MQTT Transport Microservice + + + UTF-8 + ${basedir}/../../.. + tb-mqtt-transport + tb-mqtt-transport + thingsboard + /var/log/${pkg.name} + /usr/share/${pkg.name} + + + + + org.thingsboard.transport + mqtt + ${project.version} + deb + deb + provided + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-tb-mqtt-transport-deb + package + + copy + + + + + org.thingsboard.transport + mqtt + deb + deb + ${pkg.name}.deb + ${project.build.directory} + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-docker-config + process-resources + + copy-resources + + + ${project.build.directory} + + + docker + true + + + + + + + + com.spotify + dockerfile-maven-plugin + + + build-docker-image + pre-integration-test + + build + + + + + ${dockerfile.skip} + ${docker.repo}/${docker.name} + ${project.version} + true + false + ${project.build.directory} + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + + diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml new file mode 100644 index 0000000000..9f091e3d20 --- /dev/null +++ b/msa/transport/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.thingsboard + 2.2.0-SNAPSHOT + msa + + org.thingsboard.msa + transport + pom + + ThingsBoard Transport Microservices + https://thingsboard.io + + + ${basedir}/../.. + + + + mqtt + http + coap + + + + + + + com.spotify + dockerfile-maven-plugin + 1.4.5 + + + + + + diff --git a/pom.xml b/pom.xml index d6db123ebe..387e0eda42 100755 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,7 @@ 1.5.0 2.5 1.4 - 2.8.8.1 + 2.8.11.1 2.2.6 2.11 2.4.2 @@ -82,6 +82,7 @@ 5.0.2 0.1.14 2.0.0 + 4.1.1 @@ -355,18 +356,23 @@ ${project.version} - org.thingsboard.transport - http + org.thingsboard.common.transport + transport-api ${project.version} - org.thingsboard.transport - coap + org.thingsboard.common.transport + mqtt ${project.version} - org.thingsboard.transport - mqtt + org.thingsboard.common.transport + http + ${project.version} + + + org.thingsboard.common.transport + coap ${project.version} @@ -773,6 +779,11 @@ delight-nashorn-sandbox ${delight-nashorn-sandbox.version} + + com.github.vladimir-bukhtoyarov + bucket4j-core + ${bucket4j.version} + diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 6390929911..c22ce784fa 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -45,8 +45,8 @@ provided - org.thingsboard.common - transport + org.thingsboard.common.transport + transport-api provided @@ -71,6 +71,10 @@ com.google.guava guava + + com.google.code.gson + gson + org.springframework spring-web diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 51386dc942..459e35bd21 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -17,7 +17,9 @@ package org.thingsboard.rule.engine.action; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture; +import com.google.gson.JsonElement; import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.RuleNode; @@ -25,7 +27,6 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.util.DonAsynchron; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; @@ -37,12 +38,11 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; @Slf4j @@ -68,70 +68,88 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { } @Override - public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { - if (!msg.getMetaData().getData().isEmpty()) { - long now = System.currentTimeMillis(); - String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ? - DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope"); + public void onMsg(TbContext ctx, TbMsg msg) { + if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) || + DataConstants.ATTRIBUTES_DELETED.equals(msg.getType()) || + SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { + if (!msg.getMetaData().getData().isEmpty()) { + long now = System.currentTimeMillis(); + String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ? + DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope"); - ListenableFuture> entityViewsFuture = - ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator()); + ListenableFuture> entityViewsFuture = + ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator()); - DonAsynchron.withCallback(entityViewsFuture, - entityViews -> { - for (EntityView entityView : entityViews) { - long startTime = entityView.getStartTimeMs(); - long endTime = entityView.getEndTimeMs(); - if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { - Set attributes = - JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())).getAttributes(); - List filteredAttributes = - attributes.stream() - .filter(attr -> { - switch (scope) { - case DataConstants.CLIENT_SCOPE: - if (entityView.getKeys().getAttributes().getCs().isEmpty()) { - return true; - } - return entityView.getKeys().getAttributes().getCs().contains(attr.getKey()); - case DataConstants.SERVER_SCOPE: - if (entityView.getKeys().getAttributes().getSs().isEmpty()) { - return true; - } - return entityView.getKeys().getAttributes().getSs().contains(attr.getKey()); - case DataConstants.SHARED_SCOPE: - if (entityView.getKeys().getAttributes().getSh().isEmpty()) { - return true; - } - return entityView.getKeys().getAttributes().getSh().contains(attr.getKey()); + DonAsynchron.withCallback(entityViewsFuture, + entityViews -> { + for (EntityView entityView : entityViews) { + long startTime = entityView.getStartTimeMs(); + long endTime = entityView.getEndTimeMs(); + if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { + if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) || + SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { + Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); + List filteredAttributes = + attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr.getKey(), entityView)).collect(Collectors.toList()); + ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes, + new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + transformAndTellNext(ctx, msg, entityView); } - return false; - }).collect(Collectors.toList()); - ctx.getTelemetryService().saveAndNotify(entityView.getId(), scope, filteredAttributes, - new FutureCallback() { - @Override - public void onSuccess(@Nullable Void result) { - TbMsg updMsg = ctx.transformMsg(msg, msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData()); - ctx.tellNext(updMsg, SUCCESS); - } - - @Override - public void onFailure(Throwable t) { - ctx.tellFailure(msg, t); + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }); + } else if (DataConstants.ATTRIBUTES_DELETED.equals(msg.getType())) { + List attributes = new ArrayList<>(); + for (JsonElement element : new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray()) { + if (element.isJsonPrimitive()) { + JsonPrimitive value = element.getAsJsonPrimitive(); + if (value.isString()) { + attributes.add(value.getAsString()); + } } - }); + } + List filteredAttributes = + attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr, entityView)).collect(Collectors.toList()); + if (filteredAttributes != null && !filteredAttributes.isEmpty()) { + ctx.getAttributesService().removeAll(entityView.getId(), scope, filteredAttributes); + transformAndTellNext(ctx, msg, entityView); + } + } + } } - } - }, - t -> ctx.tellFailure(msg, t)); + }, + t -> ctx.tellFailure(msg, t)); + } else { + ctx.tellFailure(msg, new IllegalArgumentException("Message metadata is empty")); + } } else { - ctx.tellNext(msg, FAILURE); + ctx.tellFailure(msg, new IllegalArgumentException("Unsupported msg type [" + msg.getType() + "]")); } } + private void transformAndTellNext(TbContext ctx, TbMsg msg, EntityView entityView) { + TbMsg updMsg = ctx.transformMsg(msg, msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData()); + ctx.tellNext(updMsg, SUCCESS); + } + + private boolean attributeContainsInEntityView(String scope, String attrKey, EntityView entityView) { + switch (scope) { + case DataConstants.CLIENT_SCOPE: + return entityView.getKeys().getAttributes().getCs().contains(attrKey); + case DataConstants.SERVER_SCOPE: + return entityView.getKeys().getAttributes().getSs().contains(attrKey); + case DataConstants.SHARED_SCOPE: + return entityView.getKeys().getAttributes().getSh().contains(attrKey); + } + return false; + } + @Override public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index f78b186b96..035a17ddc8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; configClazz = EmptyNodeConfiguration.class, relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", - "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Other"}, + "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other"}, nodeDescription = "Route incoming messages by Message Type", nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\" etc. via corresponding chain, otherwise Other chain is used.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -75,6 +75,10 @@ public class TbMsgTypeSwitchNode implements TbNode { relationType = "Attributes Updated"; } else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) { relationType = "Attributes Deleted"; + } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { + relationType = "Alarm Acknowledged"; + } else if (msg.getType().equals(DataConstants.ALARM_CLEAR)) { + relationType = "Alarm Cleared"; } else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) { relationType = "RPC Request to Device"; } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 5cdb04ed5b..12dbc6dd89 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -63,7 +63,7 @@ public class TbMsgAttributesNode implements TbNode { } String src = msg.getData(); - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes(); + Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)); ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg)); if (msg.getOriginator().getEntityType() == EntityType.DEVICE && DataConstants.SHARED_SCOPE.equals(config.getScope())) { ctx.getTelemetryService().onSharedAttributesUpdate(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId()), attributes); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index efdf4af3f2..4cb9999969 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -68,13 +67,13 @@ public class TbMsgTimeseriesNode implements TbNode { if (!StringUtils.isEmpty(tsStr)) { try { ts = Long.parseLong(tsStr); - } catch (NumberFormatException e) {} + } catch (NumberFormatException e) { + } } else { ts = System.currentTimeMillis(); } String src = msg.getData(); - TelemetryUploadRequest telemetryUploadRequest = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts); - Map> tsKvMap = telemetryUploadRequest.getData(); + Map> tsKvMap = JsonConverter.convertToTelemetry(new JsonParser().parse(src), ts); if (tsKvMap == null) { ctx.tellFailure(msg, new IllegalArgumentException("Msg body is empty: " + src)); return; diff --git a/transport/coap/build.gradle b/transport/coap/build.gradle new file mode 100644 index 0000000000..6d54cb4789 --- /dev/null +++ b/transport/coap/build.gradle @@ -0,0 +1,140 @@ +/** + * 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. + */ +import org.apache.tools.ant.filters.ReplaceTokens + +buildscript { + ext { + osPackageVersion = "3.8.0" + } + repositories { + jcenter() + } + dependencies { + classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}") + } +} + +apply plugin: "nebula.ospackage" + +buildDir = projectBuildDir +version = projectVersion +distsDirName = "./" + +// OS Package plugin configuration +ospackage { + packageName = pkgName + version = "${project.version}" + release = 1 + os = LINUX + type = BINARY + + into pkgInstallFolder + + user pkgName + permissionGroup pkgName + + // Copy the actual .jar file + from(mainJar) { + // Strip the version from the jar filename + rename { String fileName -> + "${pkgName}.jar" + } + fileMode 0500 + into "bin" + } + + // Copy the config files + from("target/conf") { + exclude "${pkgName}.conf" + fileType CONFIG | NOREPLACE + fileMode 0754 + into "conf" + } + +} + +// Configure our RPM build task +buildRpm { + + arch = NOARCH + + version = projectVersion.replace('-', '') + archiveName = "${pkgName}.rpm" + + requires("java-1.8.0") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + preInstall file("${buildDir}/control/rpm/preinst") + postInstall file("${buildDir}/control/rpm/postinst") + preUninstall file("${buildDir}/control/rpm/prerm") + postUninstall file("${buildDir}/control/rpm/postrm") + + user pkgName + permissionGroup pkgName + + // Copy the system unit files + from("${buildDir}/control/${pkgName}.service") { + addParentDirs = false + fileMode 0644 + into "/usr/lib/systemd/system" + } + + directory(pkgLogFolder, 0755) + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} + +// Same as the buildRpm task +buildDeb { + + arch = "all" + + archiveName = "${pkgName}.deb" + + requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'deb']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf") + configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml") + configurationFile("${pkgInstallFolder}/conf/logback.xml") + + preInstall file("${buildDir}/control/deb/preinst") + postInstall file("${buildDir}/control/deb/postinst") + preUninstall file("${buildDir}/control/deb/prerm") + postUninstall file("${buildDir}/control/deb/postrm") + + user pkgName + permissionGroup pkgName + + directory(pkgLogFolder, 0755) + link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar") + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index a7015f0a35..a5e8107ab6 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -27,42 +27,37 @@ coap jar - Thingsboard COAP Transport + Thingsboard CoAP Transport Service https://thingsboard.io UTF-8 ${basedir}/../.. + tb-coap-transport + /var/log/${pkg.name} + /usr/share/${pkg.name} + ${project.build.directory}/windows - org.thingsboard.common - transport - - - org.eclipse.californium - californium-core - - - org.springframework - spring-context + org.thingsboard.common.transport + coap - org.slf4j - slf4j-api - - - org.slf4j - log4j-over-slf4j + org.thingsboard.common + queue - ch.qos.logback - logback-core + org.springframework.boot + spring-boot-starter-web - ch.qos.logback - logback-classic + com.sun.winsw + winsw + bin + exe + provided org.springframework.boot @@ -81,4 +76,266 @@ + + ${pkg.name}-${project.version} + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/resources + + logback.xml + + false + + + + + + copy-service-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/conf + true + + + + src/main/filters/unix.properties + + + + + copy-win-conf + process-resources + + copy-resources + + + ${pkg.win.dist}/conf + + + src/main/resources + + logback.xml + + false + + + src/main/conf + + tb-coap-transport.conf + + true + + + + src/main/filters/windows.properties + + + + + copy-control + process-resources + + copy-resources + + + ${project.build.directory}/control + + + src/main/scripts/control + true + + + + src/main/filters/unix.properties + + + + + copy-windows-control + process-resources + + copy-resources + + + ${pkg.win.dist} + + + src/main/scripts/windows + true + + + + src/main/filters/windows.properties + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-winsw-service + package + + copy + + + + + com.sun.winsw + winsw + bin + exe + service.exe + + + ${pkg.win.dist} + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + **/logback.xml + + + + ThingsBoard CoAP Transport Service + ${project.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + org.thingsboard.server.coap.ThingsboardCoapTransportApplication + boot + ZIP + true + true + + ${pkg.installFolder}/conf + ${pkg.unixLogFolder} + ${pkg.name}.out + ${pkg.name} + + + + + + repackage + + + + + + org.fortasoft + gradle-maven-plugin + + + build + buildDeb + buildRpm + + + -PprojectBuildDir=${project.build.directory} + -PprojectVersion=${project.version} + -PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + -PpkgName=${pkg.name} + -PpkgInstallFolder=${pkg.installFolder} + -PpkgLogFolder=${pkg.unixLogFolder} + + + + + package + + invoke + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + ${pkg.name} + + src/main/assembly/windows.xml + + + + + assembly + package + + single + + + + + + org.apache.maven.plugins + maven-install-plugin + + ${project.build.directory}/${pkg.name}.deb + ${project.artifactId} + ${project.groupId} + ${project.version} + deb + deb + + + + install-deb + package + + install-file + + + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + diff --git a/transport/coap/src/main/assembly/windows.xml b/transport/coap/src/main/assembly/windows.xml new file mode 100644 index 0000000000..82da34ed13 --- /dev/null +++ b/transport/coap/src/main/assembly/windows.xml @@ -0,0 +1,71 @@ + + + windows + + + zip + + + + + + ${pkg.win.dist} + logs + + */** + + + + ${pkg.win.dist}/conf + conf + windows + + + + + + ${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + lib + ${pkg.name}.jar + + + ${pkg.win.dist}/service.exe + + ${pkg.name}.exe + + + ${pkg.win.dist}/service.xml + + ${pkg.name}.xml + windows + + + ${pkg.win.dist}/install.bat + + windows + + + ${pkg.win.dist}/uninstall.bat + + windows + + + diff --git a/transport/coap/src/main/conf/logback.xml b/transport/coap/src/main/conf/logback.xml new file mode 100644 index 0000000000..f36469d5f2 --- /dev/null +++ b/transport/coap/src/main/conf/logback.xml @@ -0,0 +1,43 @@ + + + + + + + ${pkg.logFolder}/${pkg.name}.log + + ${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/transport/coap/src/main/conf/tb-coap-transport.conf b/transport/coap/src/main/conf/tb-coap-transport.conf new file mode 100644 index 0000000000..0afa91c18e --- /dev/null +++ b/transport/coap/src/main/conf/tb-coap-transport.conf @@ -0,0 +1,23 @@ +# +# 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" +export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" +export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/coap/src/main/filters/unix.properties b/transport/coap/src/main/filters/unix.properties new file mode 100644 index 0000000000..8967278673 --- /dev/null +++ b/transport/coap/src/main/filters/unix.properties @@ -0,0 +1 @@ +pkg.logFolder=${pkg.unixLogFolder} \ No newline at end of file diff --git a/transport/coap/src/main/filters/windows.properties b/transport/coap/src/main/filters/windows.properties new file mode 100644 index 0000000000..a6e48d91ba --- /dev/null +++ b/transport/coap/src/main/filters/windows.properties @@ -0,0 +1,2 @@ +pkg.logFolder=${BASE}\\logs +pkg.winWrapperLogFolder=%BASE%\\logs diff --git a/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java new file mode 100644 index 0000000000..1ad86dd166 --- /dev/null +++ b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java @@ -0,0 +1,48 @@ +/** + * 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.coap; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.Arrays; + +@SpringBootConfiguration +@EnableAsync +@EnableScheduling +@ComponentScan({"org.thingsboard.server.coap", "org.thingsboard.server.common", "org.thingsboard.server.transport.coap", "org.thingsboard.server.kafka"}) +public class ThingsboardCoapTransportApplication { + + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-coap-transport"; + + public static void main(String[] args) { + SpringApplication.run(ThingsboardCoapTransportApplication.class, updateArguments(args)); + } + + private static String[] updateArguments(String[] args) { + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { + String[] modifiedArgs = new String[args.length + 1]; + System.arraycopy(args, 0, modifiedArgs, 0, args.length); + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; + return modifiedArgs; + } + return args; + } +} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java deleted file mode 100644 index 8a596d7119..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ /dev/null @@ -1,243 +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.transport.coap; - -import java.lang.reflect.Field; -import java.util.List; -import java.util.Optional; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapResource; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.network.Exchange; -import org.eclipse.californium.core.network.ExchangeObserver; -import org.eclipse.californium.core.server.resources.CoapExchange; -import org.eclipse.californium.core.server.resources.Resource; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.QuotaService; -import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; -import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy; -import org.thingsboard.server.transport.coap.session.CoapSessionCtx; -import org.springframework.util.ReflectionUtils; - -@Slf4j -public class CoapTransportResource extends CoapResource { - // coap://localhost:port/api/v1/DEVICE_TOKEN/[attributes|telemetry|rpc[/requestId]] - private static final int ACCESS_TOKEN_POSITION = 3; - private static final int FEATURE_TYPE_POSITION = 4; - private static final int REQUEST_ID_POSITION = 5; - - private final CoapTransportAdaptor adaptor; - private final SessionMsgProcessor processor; - private final DeviceAuthService authService; - private final QuotaService quotaService; - private final Field observerField; - private final long timeout; - - public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name, - long timeout, QuotaService quotaService) { - super(name); - this.processor = processor; - this.authService = authService; - this.quotaService = quotaService; - this.adaptor = adaptor; - this.timeout = timeout; - // This is important to turn off existing observable logic in - // CoapResource. We will have our own observe monitoring due to 1:1 - // observe relationship. - this.setObservable(false); - observerField = ReflectionUtils.findField(Exchange.class, "observer"); - observerField.setAccessible(true); - } - - @Override - public void handleGET(CoapExchange exchange) { - if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { - log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); - exchange.respond(ResponseCode.BAD_REQUEST); - return; - } - - Optional featureType = getFeatureType(exchange.advanced().getRequest()); - if (!featureType.isPresent()) { - log.trace("Missing feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); - } else if (featureType.get() == FeatureType.TELEMETRY) { - log.trace("Can't fetch/subscribe to timeseries updates"); - exchange.respond(ResponseCode.BAD_REQUEST); - } else if (exchange.getRequestOptions().hasObserve()) { - processExchangeGetRequest(exchange, featureType.get()); - } else if (featureType.get() == FeatureType.ATTRIBUTES) { - processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); - } else { - log.trace("Invalid feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); - } - } - - private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { - boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; - SessionMsgType sessionMsgType; - if (featureType == FeatureType.RPC) { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; - } else { - sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; - } - Optional sessionId = processRequest(exchange, sessionMsgType); - if (sessionId.isPresent()) { - if (exchange.getRequestOptions().getObserve() == 1) { - exchange.respond(ResponseCode.VALID); - } - } - } - - @Override - public void handlePOST(CoapExchange exchange) { - if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) { - log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort()); - exchange.respond(ResponseCode.BAD_REQUEST); - return; - } - - Optional featureType = getFeatureType(exchange.advanced().getRequest()); - if (!featureType.isPresent()) { - log.trace("Missing feature type parameter"); - exchange.respond(ResponseCode.BAD_REQUEST); - } else { - switch (featureType.get()) { - case ATTRIBUTES: - processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); - break; - case TELEMETRY: - processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); - break; - case RPC: - Optional requestId = getRequestId(exchange.advanced().getRequest()); - if (requestId.isPresent()) { - processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); - } else { - processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); - } - break; - } - } - } - - private Optional processRequest(CoapExchange exchange, SessionMsgType type) { - log.trace("Processing {}", exchange.advanced().getRequest()); - exchange.accept(); - Exchange advanced = exchange.advanced(); - Request request = advanced.getRequest(); - - Optional credentials = decodeCredentials(request); - if (!credentials.isPresent()) { - exchange.respond(ResponseCode.BAD_REQUEST); - return Optional.empty(); - } - - CoapSessionCtx ctx = new CoapSessionCtx(exchange, adaptor, processor, authService, timeout); - - if (!ctx.login(credentials.get())) { - exchange.respond(ResponseCode.UNAUTHORIZED); - return Optional.empty(); - } - - AdaptorToSessionActorMsg msg; - try { - switch (type) { - case GET_ATTRIBUTES_REQUEST: - case POST_ATTRIBUTES_REQUEST: - case POST_TELEMETRY_REQUEST: - case TO_DEVICE_RPC_RESPONSE: - case TO_SERVER_RPC_REQUEST: - ctx.setSessionType(SessionType.SYNC); - msg = adaptor.convertToActorMsg(ctx, type, request); - break; - case SUBSCRIBE_ATTRIBUTES_REQUEST: - case SUBSCRIBE_RPC_COMMANDS_REQUEST: - ExchangeObserver systemObserver = (ExchangeObserver) observerField.get(advanced); - advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx)); - ctx.setSessionType(SessionType.ASYNC); - msg = adaptor.convertToActorMsg(ctx, type, request); - break; - case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - ctx.setSessionType(SessionType.ASYNC); - msg = adaptor.convertToActorMsg(ctx, type, request); - break; - default: - log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type); - throw new IllegalArgumentException("Unsupported msg type: " + type); - } - log.trace("Processing msg: {}", msg); - processor.process(new BasicTransportToDeviceSessionActorMsg(ctx.getDevice(), msg)); - } catch (AdaptorException e) { - log.debug("Failed to decode payload {}", e); - exchange.respond(ResponseCode.BAD_REQUEST, e.getMessage()); - return Optional.empty(); - } catch (IllegalArgumentException | IllegalAccessException e) { - log.debug("Failed to process payload {}", e); - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR, e.getMessage()); - } - return Optional.of(ctx.getSessionId()); - } - - private Optional decodeCredentials(Request request) { - List uriPath = request.getOptions().getUriPath(); - DeviceCredentialsFilter credentials = null; - if (uriPath.size() >= ACCESS_TOKEN_POSITION) { - credentials = new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1)); - } - return Optional.ofNullable(credentials); - } - - private Optional getFeatureType(Request request) { - List uriPath = request.getOptions().getUriPath(); - try { - if (uriPath.size() >= FEATURE_TYPE_POSITION) { - return Optional.of(FeatureType.valueOf(uriPath.get(FEATURE_TYPE_POSITION - 1).toUpperCase())); - } - } catch (RuntimeException e) { - log.warn("Failed to decode feature type: {}", uriPath); - } - return Optional.empty(); - } - - public static Optional getRequestId(Request request) { - List uriPath = request.getOptions().getUriPath(); - try { - if (uriPath.size() >= REQUEST_ID_POSITION) { - return Optional.of(Integer.valueOf(uriPath.get(REQUEST_ID_POSITION - 1))); - } - } catch (RuntimeException e) { - log.warn("Failed to decode feature type: {}", uriPath); - } - return Optional.empty(); - } - - @Override - public Resource getChild(String name) { - return this; - } - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java deleted file mode 100644 index d9a04b163a..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java +++ /dev/null @@ -1,25 +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.transport.coap.adaptors; - -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.thingsboard.server.common.transport.TransportAdaptor; -import org.thingsboard.server.transport.coap.session.CoapSessionCtx; - -public interface CoapTransportAdaptor extends TransportAdaptor { - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java deleted file mode 100644 index 1c9631129f..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ /dev/null @@ -1,265 +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.transport.coap.adaptors; - -import java.util.*; - -import com.google.gson.JsonElement; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.BasicAdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; -import org.thingsboard.server.common.msg.session.SessionContext; -import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.springframework.stereotype.Component; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; -import org.thingsboard.server.transport.coap.CoapTransportResource; -import org.thingsboard.server.transport.coap.session.CoapSessionCtx; - -@Component("JsonCoapAdaptor") -@Slf4j -public class JsonCoapAdaptor implements CoapTransportAdaptor { - - @Override - public AdaptorToSessionActorMsg convertToActorMsg(CoapSessionCtx ctx, SessionMsgType type, Request inbound) throws AdaptorException { - FromDeviceMsg msg = null; - switch (type) { - case POST_TELEMETRY_REQUEST: - msg = convertToTelemetryUploadRequest(ctx, inbound); - break; - case POST_ATTRIBUTES_REQUEST: - msg = convertToUpdateAttributesRequest(ctx, inbound); - break; - case GET_ATTRIBUTES_REQUEST: - msg = convertToGetAttributesRequest(ctx, inbound); - break; - case SUBSCRIBE_RPC_COMMANDS_REQUEST: - msg = new RpcSubscribeMsg(); - break; - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - msg = new RpcUnsubscribeMsg(); - break; - case TO_DEVICE_RPC_RESPONSE: - msg = convertToDeviceRpcResponse(ctx, inbound); - break; - case TO_SERVER_RPC_REQUEST: - msg = convertToServerRpcRequest(ctx, inbound); - break; - case SUBSCRIBE_ATTRIBUTES_REQUEST: - msg = new AttributesSubscribeMsg(); - break; - case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - msg = new AttributesUnsubscribeMsg(); - break; - default: - log.warn("[{}] Unsupported msg type: {}!", ctx.getSessionId(), type); - throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + type + "!")); - } - return new BasicAdaptorToSessionActorMsg(ctx, msg); - } - - private FromDeviceMsg convertToDeviceRpcResponse(CoapSessionCtx ctx, Request inbound) throws AdaptorException { - Optional requestId = CoapTransportResource.getRequestId(inbound); - String payload = validatePayload(ctx, inbound); - JsonObject response = new JsonParser().parse(payload).getAsJsonObject(); - return new ToDeviceRpcResponseMsg( - requestId.orElseThrow(() -> new AdaptorException("Request id is missing!")), - response.get("response").toString()); - } - - private FromDeviceMsg convertToServerRpcRequest(CoapSessionCtx ctx, Request inbound) throws AdaptorException { - - String payload = validatePayload(ctx, inbound); - - return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), 0); - } - - @Override - public Optional convertToAdaptorMsg(CoapSessionCtx ctx, SessionActorToAdaptorMsg source) throws AdaptorException { - ToDeviceMsg msg = source.getMsg(); - switch (msg.getSessionMsgType()) { - case STATUS_CODE_RESPONSE: - case TO_DEVICE_RPC_RESPONSE_ACK: - return Optional.of(convertStatusCodeResponse((StatusCodeResponse) msg)); - case GET_ATTRIBUTES_RESPONSE: - return Optional.of(convertGetAttributesResponse((GetAttributesResponse) msg)); - case ATTRIBUTES_UPDATE_NOTIFICATION: - return Optional.of(convertNotificationResponse(ctx, (AttributesUpdateNotification) msg)); - case TO_DEVICE_RPC_REQUEST: - return Optional.of(convertToDeviceRpcRequest(ctx, (ToDeviceRpcRequestMsg) msg)); - case TO_SERVER_RPC_RESPONSE: - return Optional.of(convertToServerRpcResponse(ctx, (ToServerRpcResponseMsg) msg)); - case RULE_ENGINE_ERROR: - return Optional.of(convertToRuleEngineErrorResponse(ctx, (RuleEngineErrorMsg) msg)); - default: - log.warn("[{}] Unsupported msg type: {}!", source.getSessionId(), msg.getSessionMsgType()); - throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + msg.getSessionMsgType() + "!")); - } - } - - private Response convertToRuleEngineErrorResponse(CoapSessionCtx ctx, RuleEngineErrorMsg msg) { - ResponseCode status = ResponseCode.INTERNAL_SERVER_ERROR; - switch (msg.getError()) { - case QUEUE_PUT_TIMEOUT: - status = ResponseCode.GATEWAY_TIMEOUT; - break; - default: - if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) { - status = ResponseCode.BAD_REQUEST; - } - break; - } - Response response = new Response(status); - response.setPayload(JsonConverter.toErrorJson(msg.getErrorMsg()).toString()); - return response; - } - - private Response convertNotificationResponse(CoapSessionCtx ctx, AttributesUpdateNotification msg) { - return getObserveNotification(ctx, JsonConverter.toJson(msg.getData(), false)); - } - - private Response convertToDeviceRpcRequest(CoapSessionCtx ctx, ToDeviceRpcRequestMsg msg) { - return getObserveNotification(ctx, JsonConverter.toJson(msg, true)); - } - - private Response getObserveNotification(CoapSessionCtx ctx, JsonObject json) { - Response response = new Response(ResponseCode.CONTENT); - response.getOptions().setObserve(ctx.nextSeqNumber()); - response.setPayload(json.toString()); - return response; - } - - private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { - String payload = validatePayload(ctx, inbound); - try { - return JsonConverter.convertToAttributes(new JsonParser().parse(payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - private FromDeviceMsg convertToGetAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { - List queryElements = inbound.getOptions().getUriQuery(); - if (queryElements != null && queryElements.size() > 0) { - Set clientKeys = toKeys(ctx, queryElements, "clientKeys"); - Set sharedKeys = toKeys(ctx, queryElements, "sharedKeys"); - return new BasicGetAttributesRequest(0, clientKeys, sharedKeys); - } else { - return new BasicGetAttributesRequest(0); - } - } - - private Set toKeys(SessionContext ctx, List queryElements, String attributeName) throws AdaptorException { - String keys = null; - for (String queryElement : queryElements) { - String[] queryItem = queryElement.split("="); - if (queryItem.length == 2 && queryItem[0].equals(attributeName)) { - keys = queryItem[1]; - } - } - if (keys != null && !StringUtils.isEmpty(keys)) { - return new HashSet<>(Arrays.asList(keys.split(","))); - } else { - return null; - } - } - - private TelemetryUploadRequest convertToTelemetryUploadRequest(SessionContext ctx, Request inbound) throws AdaptorException { - String payload = validatePayload(ctx, inbound); - try { - return JsonConverter.convertToTelemetry(new JsonParser().parse(payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - private Response convertStatusCodeResponse(StatusCodeResponse msg) { - if (msg.isSuccess()) { - Optional code = msg.getData(); - if (code.isPresent() && code.get() == 200) { - return new Response(ResponseCode.VALID); - } else { - return new Response(ResponseCode.CREATED); - } - } else { - return convertError(msg.getError()); - } - } - - private String validatePayload(SessionContext ctx, Request inbound) throws AdaptorException { - String payload = inbound.getPayloadString(); - if (payload == null) { - log.warn("[{}] Payload is empty!", ctx.getSessionId()); - throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); - } - return payload; - } - - private Response convertToServerRpcResponse(SessionContext ctx, ToServerRpcResponseMsg msg) { - if (msg.isSuccess()) { - Response response = new Response(ResponseCode.CONTENT); - JsonElement result = JsonConverter.toJson(msg); - response.setPayload(result.toString()); - return response; - } else { - return convertError(Optional.of(new RuntimeException("Server RPC response is empty!"))); - } - } - - private Response convertGetAttributesResponse(GetAttributesResponse msg) { - if (msg.isSuccess()) { - Optional payload = msg.getData(); - if (!payload.isPresent() || (payload.get().getClientAttributes().isEmpty() && payload.get().getSharedAttributes().isEmpty())) { - return new Response(ResponseCode.NOT_FOUND); - } else { - Response response = new Response(ResponseCode.CONTENT); - JsonObject result = JsonConverter.toJson(payload.get(), false); - response.setPayload(result.toString()); - return response; - } - } else { - return convertError(msg.getError()); - } - } - - private Response convertError(Optional exception) { - if (exception.isPresent()) { - log.warn("Converting exception: {}", exception.get().getMessage(), exception.get()); - if (exception.get() instanceof ProcessingTimeoutException) { - return new Response(ResponseCode.SERVICE_UNAVAILABLE); - } else { - return new Response(ResponseCode.INTERNAL_SERVER_ERROR); - } - } else { - return new Response(ResponseCode.INTERNAL_SERVER_ERROR); - } - } - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java deleted file mode 100644 index 0c264817d5..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapExchangeObserverProxy.java +++ /dev/null @@ -1,38 +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.transport.coap.session; - -import org.eclipse.californium.core.network.Exchange; -import org.eclipse.californium.core.network.ExchangeObserver; - -public class CoapExchangeObserverProxy implements ExchangeObserver { - - private final ExchangeObserver proxy; - private final CoapSessionCtx ctx; - - public CoapExchangeObserverProxy(ExchangeObserver proxy, CoapSessionCtx ctx) { - super(); - this.proxy = proxy; - this.ctx = ctx; - } - - @Override - public void completed(Exchange exchange) { - proxy.completed(exchange); - ctx.close(); - } - -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java deleted file mode 100644 index 7a703b2be3..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionCtx.java +++ /dev/null @@ -1,135 +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.transport.coap.session; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.Request; -import org.eclipse.californium.core.coap.Response; -import org.eclipse.californium.core.server.resources.CoapExchange; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; -import org.thingsboard.server.common.msg.session.SessionCtrlMsg; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; -import org.thingsboard.server.common.msg.session.ex.SessionAuthException; -import org.thingsboard.server.common.msg.session.ex.SessionException; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; -import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.atomic.AtomicInteger; - -@Slf4j -public class CoapSessionCtx extends DeviceAwareSessionContext { - - private final SessionId sessionId; - private final CoapExchange exchange; - private final CoapTransportAdaptor adaptor; - private final String token; - private final long timeout; - private SessionType sessionType; - private final AtomicInteger seqNumber = new AtomicInteger(2); - - public CoapSessionCtx(CoapExchange exchange, CoapTransportAdaptor adaptor, SessionMsgProcessor processor, DeviceAuthService authService, long timeout) { - super(processor, authService); - Request request = exchange.advanced().getRequest(); - this.token = request.getTokenString(); - this.sessionId = new CoapSessionId(request.getSource().getHostAddress(), request.getSourcePort(), this.token); - this.exchange = exchange; - this.adaptor = adaptor; - this.timeout = timeout; - } - - - @Override - public void onMsg(SessionActorToAdaptorMsg msg) throws SessionException { - try { - adaptor.convertToAdaptorMsg(this, msg).ifPresent(this::pushToNetwork); - } catch (AdaptorException e) { - logAndWrap(e); - } - } - - private void pushToNetwork(Response response) { - exchange.respond(response); - } - - private void logAndWrap(AdaptorException e) throws SessionException { - log.warn("Failed to convert msg: {}", e.getMessage(), e); - throw new SessionException(e); - } - - @Override - public void onMsg(SessionCtrlMsg msg) throws SessionException { - log.debug("[{}] onCtrl: {}", sessionId, msg); - if (msg instanceof SessionCloseMsg) { - onSessionClose((SessionCloseMsg) msg); - } - } - - private void onSessionClose(SessionCloseMsg msg) { - if (msg.isTimeout()) { - exchange.respond(ResponseCode.SERVICE_UNAVAILABLE); - } else if (msg.isCredentialsRevoked()) { - exchange.respond(ResponseCode.UNAUTHORIZED); - } else { - exchange.respond(ResponseCode.INTERNAL_SERVER_ERROR); - } - } - - @Override - public SessionId getSessionId() { - return sessionId; - } - - @Override - public String toString() { - return "CoapSessionCtx [sessionId=" + sessionId + "]"; - } - - @Override - public boolean isClosed() { - return exchange.advanced().isComplete() || exchange.advanced().isTimedOut(); - } - - public void close() { - log.info("[{}] Closing processing context. Timeout: {}", sessionId, exchange.advanced().isTimedOut()); - processor.process(exchange.advanced().isTimedOut() ? SessionCloseMsg.onTimeout(sessionId) : SessionCloseMsg.onError(sessionId)); - } - - @Override - public long getTimeout() { - return timeout; - } - - public void setSessionType(SessionType sessionType) { - this.sessionType = sessionType; - } - - @Override - public SessionType getSessionType() { - return sessionType; - } - - public int nextSeqNumber() { - return seqNumber.getAndIncrement(); - } -} diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java deleted file mode 100644 index 888fb2fc30..0000000000 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/session/CoapSessionId.java +++ /dev/null @@ -1,77 +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.transport.coap.session; - -import org.thingsboard.server.common.data.id.SessionId; - -public final class CoapSessionId implements SessionId { - - private final String clientAddress; - private final int clientPort; - private final String token; - - public CoapSessionId(String host, int port, String token) { - super(); - this.clientAddress = host; - this.clientPort = port; - this.token = token; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((clientAddress == null) ? 0 : clientAddress.hashCode()); - result = prime * result + clientPort; - result = prime * result + ((token == null) ? 0 : token.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - CoapSessionId other = (CoapSessionId) obj; - if (clientAddress == null) { - if (other.clientAddress != null) - return false; - } else if (!clientAddress.equals(other.clientAddress)) - return false; - if (clientPort != other.clientPort) - return false; - if (token == null) { - if (other.token != null) - return false; - } else if (!token.equals(other.token)) - return false; - return true; - } - - @Override - public String toString() { - return "CoapSessionId [clientAddress=" + clientAddress + ", clientPort=" + clientPort + ", token=" + token + "]"; - } - - @Override - public String toUidStr() { - return clientAddress + ":" + clientPort + ":" + token; - } - -} diff --git a/transport/coap/src/main/resources/logback.xml b/transport/coap/src/main/resources/logback.xml new file mode 100644 index 0000000000..18864a9fab --- /dev/null +++ b/transport/coap/src/main/resources/logback.xml @@ -0,0 +1,34 @@ + + + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml new file mode 100644 index 0000000000..1790d822d2 --- /dev/null +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -0,0 +1,68 @@ +# +# 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. +# + +spring.main.web-environment: false +spring.main.web-application-type: none + +# MQTT server parameters +transport: + coap: + bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" + bind_port: "${COAP_BIND_PORT:5683}" + timeout: "${COAP_TIMEOUT:10000}" + +#Quota parameters +quota: + host: + # Max allowed number of API requests in interval for single host + limit: "${QUOTA_HOST_LIMIT:10000}" + # Interval duration + intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}" + # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs + ttlMs: "${QUOTA_HOST_TTL_MS:60000}" + # Interval for scheduled task that cleans expired records. TTL is used for expiring + cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}" + # Enable Host API Limits + enabled: "${QUOTA_HOST_ENABLED:true}" + # Array of whitelist hosts + whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}" + # Array of blacklist hosts + blacklist: "${QUOTA_HOST_BLACKLIST:}" + log: + topSize: 10 + intervalMin: 2 + +kafka: + enabled: true + bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" + acks: "${TB_KAFKA_ACKS:all}" + retries: "${TB_KAFKA_RETRIES:1}" + batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" + linger.ms: "${TB_KAFKA_LINGER_MS:1}" + buffer.memory: "${TB_BUFFER_MEMORY:33554432}" + transport_api: + requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" + responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" + max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" + max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" + response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" + rule_engine: + topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}" + notifications: + topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" + poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" + auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" diff --git a/transport/coap/src/main/scripts/control/deb/postinst b/transport/coap/src/main/scripts/control/deb/postinst new file mode 100644 index 0000000000..d4066c027b --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} +update-rc.d ${pkg.name} defaults + diff --git a/transport/coap/src/main/scripts/control/deb/postrm b/transport/coap/src/main/scripts/control/deb/postrm new file mode 100644 index 0000000000..61865803c3 --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/postrm @@ -0,0 +1,3 @@ +#!/bin/sh + +update-rc.d -f ${pkg.name} remove diff --git a/transport/coap/src/main/scripts/control/deb/preinst b/transport/coap/src/main/scripts/control/deb/preinst new file mode 100644 index 0000000000..6be5959285 --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/preinst @@ -0,0 +1,18 @@ +#!/bin/sh + +if ! getent group ${pkg.name} >/dev/null; then + addgroup --system ${pkg.name} +fi + +if ! getent passwd ${pkg.name} >/dev/null; then + adduser --quiet \ + --system \ + --ingroup ${pkg.name} \ + --quiet \ + --disabled-login \ + --disabled-password \ + --home ${pkg.installFolder} \ + --no-create-home \ + -gecos "Thingsboard application" \ + ${pkg.name} +fi diff --git a/transport/coap/src/main/scripts/control/deb/prerm b/transport/coap/src/main/scripts/control/deb/prerm new file mode 100644 index 0000000000..898d3efd5c --- /dev/null +++ b/transport/coap/src/main/scripts/control/deb/prerm @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then + service ${pkg.name} stop +fi diff --git a/transport/coap/src/main/scripts/control/rpm/postinst b/transport/coap/src/main/scripts/control/rpm/postinst new file mode 100644 index 0000000000..8a7a88f7e0 --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/postinst @@ -0,0 +1,9 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} + +if [ $1 -eq 1 ] ; then + # Initial installation + systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/coap/src/main/scripts/control/rpm/postrm b/transport/coap/src/main/scripts/control/rpm/postrm new file mode 100644 index 0000000000..8e1f8a2048 --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/postrm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -ge 1 ] ; then + # Package upgrade, not uninstall + systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/coap/src/main/scripts/control/rpm/preinst b/transport/coap/src/main/scripts/control/rpm/preinst new file mode 100644 index 0000000000..e19fc884c8 --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/preinst @@ -0,0 +1,6 @@ +#!/bin/sh + +getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} +getent passwd ${pkg.name} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +-c "Thingsboard application" diff --git a/transport/coap/src/main/scripts/control/rpm/prerm b/transport/coap/src/main/scripts/control/rpm/prerm new file mode 100644 index 0000000000..accb487b8e --- /dev/null +++ b/transport/coap/src/main/scripts/control/rpm/prerm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : +fi diff --git a/transport/coap/src/main/scripts/control/tb-coap-transport.service b/transport/coap/src/main/scripts/control/tb-coap-transport.service new file mode 100644 index 0000000000..d456fc03c0 --- /dev/null +++ b/transport/coap/src/main/scripts/control/tb-coap-transport.service @@ -0,0 +1,11 @@ +[Unit] +Description=${pkg.name} +After=syslog.target + +[Service] +User=${pkg.name} +ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target diff --git a/transport/coap/src/main/scripts/windows/install.bat b/transport/coap/src/main/scripts/windows/install.bat new file mode 100644 index 0000000000..dba773663c --- /dev/null +++ b/transport/coap/src/main/scripts/windows/install.bat @@ -0,0 +1,87 @@ +@ECHO OFF + +setlocal ENABLEEXTENSIONS + +@ECHO Detecting Java version installed. +:CHECK_JAVA_64 +@ECHO Detecting if it is 64 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED + +:CHECK_JAVA_32 +@ECHO Detecting if it is 32 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED + +:JAVA_INSTALLED + +@ECHO Java 1.8 found! +@ECHO Installing ${pkg.name} ... + +%BASE%${pkg.name}.exe install + +@ECHO ${pkg.name} installed successfully! + +GOTO END + +:JAVA_NOT_INSTALLED +@ECHO Java 1.8 or above is not installed +@ECHO Please go to https://java.com/ and install Java. Then retry installation. +PAUSE +GOTO END + +:END + + diff --git a/transport/coap/src/main/scripts/windows/service.xml b/transport/coap/src/main/scripts/windows/service.xml new file mode 100644 index 0000000000..f7b9d307c8 --- /dev/null +++ b/transport/coap/src/main/scripts/windows/service.xml @@ -0,0 +1,36 @@ + + ${pkg.name} + ${project.name} + ${project.description} + %BASE%\conf + ${pkg.winWrapperLogFolder} + rotate + + java + -Xloggc:%BASE%\logs\gc.log + -XX:+HeapDumpOnOutOfMemoryError + -XX:+PrintGCDetails + -XX:+PrintGCDateStamps + -XX:+PrintHeapAtGC + -XX:+PrintTenuringDistribution + -XX:+PrintGCApplicationStoppedTime + -XX:+UseGCLogFileRotation + -XX:NumberOfGCLogFiles=10 + -XX:GCLogFileSize=10M + -XX:-UseBiasedLocking + -XX:+UseTLAB + -XX:+ResizeTLAB + -XX:+PerfDisableSharedMem + -XX:+UseCondCardMark + -XX:CMSWaitDuration=10000 + -XX:+UseParNewGC + -XX:+UseConcMarkSweepGC + -XX:+CMSParallelRemarkEnabled + -XX:+CMSParallelInitialMarkEnabled + -XX:+CMSEdenChunksRecordAlways + -XX:CMSInitiatingOccupancyFraction=75 + -XX:+UseCMSInitiatingOccupancyOnly + -jar + %BASE%\lib\${pkg.name}.jar + + diff --git a/transport/coap/src/main/scripts/windows/uninstall.bat b/transport/coap/src/main/scripts/windows/uninstall.bat new file mode 100644 index 0000000000..921e4c8395 --- /dev/null +++ b/transport/coap/src/main/scripts/windows/uninstall.bat @@ -0,0 +1,9 @@ +@ECHO OFF + +@ECHO Stopping ${pkg.name} ... +net stop ${pkg.name} + +@ECHO Uninstalling ${pkg.name} ... +%~dp0${pkg.name}.exe uninstall + +@ECHO DONE. \ No newline at end of file diff --git a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java deleted file mode 100644 index e2fd7cd6c4..0000000000 --- a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java +++ /dev/null @@ -1,208 +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.transport.coap; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP.ResponseCode; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.annotation.DirtiesContext.ClassMode; -import org.springframework.test.context.junit4.SpringRunner; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.msg.aware.SessionAwareMsg; -import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse; -import org.thingsboard.server.common.msg.core.BasicRequest; -import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; -import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.auth.DeviceAuthResult; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -@RunWith(SpringRunner.class) -@DirtiesContext(classMode = ClassMode.BEFORE_CLASS) -@Slf4j -public class CoapServerTest { - - private static final int TEST_PORT = 5555; - private static final String TELEMETRY_POST_MESSAGE = "[{\"key1\":\"value1\"}]"; - private static final String TEST_ATTRIBUTES_RESPONSE = "{\"key1\":\"value1\",\"key2\":42}"; - private static final String DEVICE1_TOKEN = "Device1Token"; - - @Configuration - public static class EchoCoapServerITConfiguration extends CoapServerTestConfiguration { - - @Bean - public static DeviceAuthService authService() { - return new DeviceAuthService() { - - private final DeviceId devId = new DeviceId(UUID.randomUUID()); - - @Override - public DeviceAuthResult process(DeviceCredentialsFilter credentials) { - if (credentials != null && credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { - DeviceTokenCredentials tokenCredentials = (DeviceTokenCredentials) credentials; - if (tokenCredentials.getCredentialsId().equals(DEVICE1_TOKEN)) { - return DeviceAuthResult.of(devId); - } - } - return DeviceAuthResult.of("Credentials are invalid!"); - } - - @Override - public Optional findDeviceById(DeviceId deviceId) { - if (deviceId.equals(devId)) { - Device dev = new Device(); - dev.setId(devId); - dev.setTenantId(new TenantId(UUID.randomUUID())); - dev.setCustomerId(new CustomerId(UUID.randomUUID())); - return Optional.of(dev); - } else { - return Optional.empty(); - } - } - }; - } - - @Bean - public static SessionMsgProcessor sessionMsgProcessor() { - return new SessionMsgProcessor() { - - @Override - public void process(SessionAwareMsg toActorMsg) { - if (toActorMsg instanceof TransportToDeviceSessionActorMsg) { - AdaptorToSessionActorMsg sessionMsg = ((TransportToDeviceSessionActorMsg) toActorMsg).getSessionMsg(); - try { - FromDeviceMsg deviceMsg = sessionMsg.getMsg(); - ToDeviceMsg toDeviceMsg = null; - if (deviceMsg.getMsgType() == SessionMsgType.POST_TELEMETRY_REQUEST) { - toDeviceMsg = BasicStatusCodeResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID); - } else if (deviceMsg.getMsgType() == SessionMsgType.GET_ATTRIBUTES_REQUEST) { - List data = new ArrayList<>(); - data.add(new BaseAttributeKvEntry(new StringDataEntry("key1", "value1"), System.currentTimeMillis())); - data.add(new BaseAttributeKvEntry(new LongDataEntry("key2", 42L), System.currentTimeMillis())); - BasicAttributeKVMsg kv = BasicAttributeKVMsg.fromClient(data); - toDeviceMsg = BasicGetAttributesResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID, kv); - } - if (toDeviceMsg != null) { - sessionMsg.getSessionContext().onMsg(new BasicSessionActorToAdaptorMsg(sessionMsg.getSessionContext(), toDeviceMsg)); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - @Override - public void onDeviceAdded(Device device) { - - } - - - }; - } - - @Bean - public static HostRequestsQuotaService quotaService() { - return new HostRequestsQuotaService(null, null, null, null, false); - } - } - - @Autowired - private CoapTransportService service; - - @Before - public void beforeTest() { - log.info("Service info: {}", service.toString()); - } - - @Test - public void testBadJsonTelemetryPostRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.TELEMETRY.name().toLowerCase()); - CoapResponse response = client.setTimeout(6000).post("test", MediaTypeRegistry.APPLICATION_JSON); - Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - @Test - public void testNoCredentialsPostRequest() { - CoapClient client = new CoapClient(getBaseTestUrl()); - CoapResponse response = client.setTimeout(6000).post(TELEMETRY_POST_MESSAGE, MediaTypeRegistry.APPLICATION_JSON); - Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - @Test - public void testValidJsonTelemetryPostRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.TELEMETRY.name().toLowerCase()); - CoapResponse response = client.setTimeout(6000).post(TELEMETRY_POST_MESSAGE, MediaTypeRegistry.APPLICATION_JSON); - Assert.assertEquals(ResponseCode.CREATED, response.getCode()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - @Test - public void testNoCredentialsAttributesGetRequest() { - CoapClient client = new CoapClient("coap://localhost:5555/api/v1?keys=key1,key2"); - CoapResponse response = client.setTimeout(6000).get(); - Assert.assertEquals(ResponseCode.BAD_REQUEST, response.getCode()); - } - - @Test - public void testNoKeysAttributesGetRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.ATTRIBUTES.name().toLowerCase() + "?data=key1,key2"); - CoapResponse response = client.setTimeout(6000).get(); - Assert.assertEquals(ResponseCode.CONTENT, response.getCode()); - } - - @Test - public void testValidAttributesGetRequest() { - CoapClient client = new CoapClient(getBaseTestUrl() + DEVICE1_TOKEN + "/" + FeatureType.ATTRIBUTES.name().toLowerCase() + "?clientKeys=key1,key2"); - CoapResponse response = client.setTimeout(6000).get(); - Assert.assertEquals(ResponseCode.CONTENT, response.getCode()); - Assert.assertEquals(TEST_ATTRIBUTES_RESPONSE, response.getResponseText()); - log.info("Response: {}, {}", response.getCode(), response.getResponseText()); - } - - private String getBaseTestUrl() { - return "coap://localhost:" + TEST_PORT + "/api/v1/"; - } - -} diff --git a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java deleted file mode 100644 index f2cd080293..0000000000 --- a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTestConfiguration.java +++ /dev/null @@ -1,35 +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.transport.coap; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.PropertySource; -import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; -import org.springframework.test.context.TestPropertySource; - -@Configuration -@ComponentScan({ "org.thingsboard.server.transport.coap" }) -@PropertySource("classpath:coap-transport-test.properties") -public class CoapServerTestConfiguration { - - @Bean - public static PropertySourcesPlaceholderConfigurer propertyConfig() { - return new PropertySourcesPlaceholderConfigurer(); - } - -} diff --git a/transport/coap/src/test/resources/coap-transport-test.properties b/transport/coap/src/test/resources/coap-transport-test.properties deleted file mode 100644 index 469b0348b1..0000000000 --- a/transport/coap/src/test/resources/coap-transport-test.properties +++ /dev/null @@ -1,4 +0,0 @@ -coap.bind_address=0.0.0.0 -coap.bind_port=5555 -coap.adaptor=JsonCoapAdaptor -coap.timeout=10000 \ No newline at end of file diff --git a/transport/http/build.gradle b/transport/http/build.gradle new file mode 100644 index 0000000000..6d54cb4789 --- /dev/null +++ b/transport/http/build.gradle @@ -0,0 +1,140 @@ +/** + * 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. + */ +import org.apache.tools.ant.filters.ReplaceTokens + +buildscript { + ext { + osPackageVersion = "3.8.0" + } + repositories { + jcenter() + } + dependencies { + classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}") + } +} + +apply plugin: "nebula.ospackage" + +buildDir = projectBuildDir +version = projectVersion +distsDirName = "./" + +// OS Package plugin configuration +ospackage { + packageName = pkgName + version = "${project.version}" + release = 1 + os = LINUX + type = BINARY + + into pkgInstallFolder + + user pkgName + permissionGroup pkgName + + // Copy the actual .jar file + from(mainJar) { + // Strip the version from the jar filename + rename { String fileName -> + "${pkgName}.jar" + } + fileMode 0500 + into "bin" + } + + // Copy the config files + from("target/conf") { + exclude "${pkgName}.conf" + fileType CONFIG | NOREPLACE + fileMode 0754 + into "conf" + } + +} + +// Configure our RPM build task +buildRpm { + + arch = NOARCH + + version = projectVersion.replace('-', '') + archiveName = "${pkgName}.rpm" + + requires("java-1.8.0") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + preInstall file("${buildDir}/control/rpm/preinst") + postInstall file("${buildDir}/control/rpm/postinst") + preUninstall file("${buildDir}/control/rpm/prerm") + postUninstall file("${buildDir}/control/rpm/postrm") + + user pkgName + permissionGroup pkgName + + // Copy the system unit files + from("${buildDir}/control/${pkgName}.service") { + addParentDirs = false + fileMode 0644 + into "/usr/lib/systemd/system" + } + + directory(pkgLogFolder, 0755) + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} + +// Same as the buildRpm task +buildDeb { + + arch = "all" + + archiveName = "${pkgName}.deb" + + requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'deb']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf") + configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml") + configurationFile("${pkgInstallFolder}/conf/logback.xml") + + preInstall file("${buildDir}/control/deb/preinst") + postInstall file("${buildDir}/control/deb/postinst") + preUninstall file("${buildDir}/control/deb/prerm") + postUninstall file("${buildDir}/control/deb/postrm") + + user pkgName + permissionGroup pkgName + + directory(pkgLogFolder, 0755) + link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar") + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 996470832c..407d995381 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -27,39 +27,37 @@ http jar - Thingsboard HTTP Transport + Thingsboard HTTP Transport Service https://thingsboard.io UTF-8 ${basedir}/../.. + tb-http-transport + /var/log/${pkg.name} + /usr/share/${pkg.name} + ${project.build.directory}/windows - org.thingsboard.common - transport - - - org.springframework.boot - spring-boot-starter-web - provided + org.thingsboard.common.transport + http - org.slf4j - slf4j-api - - - org.slf4j - log4j-over-slf4j + org.thingsboard.common + queue - ch.qos.logback - logback-core + com.sun.winsw + winsw + bin + exe + provided - ch.qos.logback - logback-classic + org.springframework.boot + spring-boot-starter-web org.springframework.boot @@ -78,4 +76,266 @@ + + ${pkg.name}-${project.version} + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/resources + + logback.xml + + false + + + + + + copy-service-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/conf + true + + + + src/main/filters/unix.properties + + + + + copy-win-conf + process-resources + + copy-resources + + + ${pkg.win.dist}/conf + + + src/main/resources + + logback.xml + + false + + + src/main/conf + + tb-http-transport.conf + + true + + + + src/main/filters/windows.properties + + + + + copy-control + process-resources + + copy-resources + + + ${project.build.directory}/control + + + src/main/scripts/control + true + + + + src/main/filters/unix.properties + + + + + copy-windows-control + process-resources + + copy-resources + + + ${pkg.win.dist} + + + src/main/scripts/windows + true + + + + src/main/filters/windows.properties + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-winsw-service + package + + copy + + + + + com.sun.winsw + winsw + bin + exe + service.exe + + + ${pkg.win.dist} + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + **/logback.xml + + + + ThingsBoard HTTP Transport Service + ${project.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + org.thingsboard.server.http.ThingsboardHttpTransportApplication + boot + ZIP + true + true + + ${pkg.installFolder}/conf + ${pkg.unixLogFolder} + ${pkg.name}.out + ${pkg.name} + + + + + + repackage + + + + + + org.fortasoft + gradle-maven-plugin + + + build + buildDeb + buildRpm + + + -PprojectBuildDir=${project.build.directory} + -PprojectVersion=${project.version} + -PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + -PpkgName=${pkg.name} + -PpkgInstallFolder=${pkg.installFolder} + -PpkgLogFolder=${pkg.unixLogFolder} + + + + + package + + invoke + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + ${pkg.name} + + src/main/assembly/windows.xml + + + + + assembly + package + + single + + + + + + org.apache.maven.plugins + maven-install-plugin + + ${project.build.directory}/${pkg.name}.deb + ${project.artifactId} + ${project.groupId} + ${project.version} + deb + deb + + + + install-deb + package + + install-file + + + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + diff --git a/transport/http/src/main/assembly/windows.xml b/transport/http/src/main/assembly/windows.xml new file mode 100644 index 0000000000..82da34ed13 --- /dev/null +++ b/transport/http/src/main/assembly/windows.xml @@ -0,0 +1,71 @@ + + + windows + + + zip + + + + + + ${pkg.win.dist} + logs + + */** + + + + ${pkg.win.dist}/conf + conf + windows + + + + + + ${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + lib + ${pkg.name}.jar + + + ${pkg.win.dist}/service.exe + + ${pkg.name}.exe + + + ${pkg.win.dist}/service.xml + + ${pkg.name}.xml + windows + + + ${pkg.win.dist}/install.bat + + windows + + + ${pkg.win.dist}/uninstall.bat + + windows + + + diff --git a/transport/http/src/main/conf/logback.xml b/transport/http/src/main/conf/logback.xml new file mode 100644 index 0000000000..f36469d5f2 --- /dev/null +++ b/transport/http/src/main/conf/logback.xml @@ -0,0 +1,43 @@ + + + + + + + ${pkg.logFolder}/${pkg.name}.log + + ${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/transport/http/src/main/conf/tb-http-transport.conf b/transport/http/src/main/conf/tb-http-transport.conf new file mode 100644 index 0000000000..0afa91c18e --- /dev/null +++ b/transport/http/src/main/conf/tb-http-transport.conf @@ -0,0 +1,23 @@ +# +# 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" +export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" +export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/http/src/main/filters/unix.properties b/transport/http/src/main/filters/unix.properties new file mode 100644 index 0000000000..8967278673 --- /dev/null +++ b/transport/http/src/main/filters/unix.properties @@ -0,0 +1 @@ +pkg.logFolder=${pkg.unixLogFolder} \ No newline at end of file diff --git a/transport/http/src/main/filters/windows.properties b/transport/http/src/main/filters/windows.properties new file mode 100644 index 0000000000..a6e48d91ba --- /dev/null +++ b/transport/http/src/main/filters/windows.properties @@ -0,0 +1,2 @@ +pkg.logFolder=${BASE}\\logs +pkg.winWrapperLogFolder=%BASE%\\logs diff --git a/transport/http/src/main/java/org/thingsboard/server/http/ThingsboardHttpTransportApplication.java b/transport/http/src/main/java/org/thingsboard/server/http/ThingsboardHttpTransportApplication.java new file mode 100644 index 0000000000..517794c346 --- /dev/null +++ b/transport/http/src/main/java/org/thingsboard/server/http/ThingsboardHttpTransportApplication.java @@ -0,0 +1,47 @@ +/** + * 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.http; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; + +import java.util.Arrays; + +@SpringBootApplication +@EnableAsync +@ComponentScan({"org.thingsboard.server.http", "org.thingsboard.server.common", "org.thingsboard.server.transport.http", "org.thingsboard.server.kafka"}) +public class ThingsboardHttpTransportApplication { + + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-http-transport"; + + public static void main(String[] args) { + SpringApplication.run(ThingsboardHttpTransportApplication.class, updateArguments(args)); + } + + private static String[] updateArguments(String[] args) { + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { + String[] modifiedArgs = new String[args.length + 1]; + System.arraycopy(args, 0, modifiedArgs, 0, args.length); + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; + return modifiedArgs; + } + return args; + } +} diff --git a/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java deleted file mode 100644 index d26d076b21..0000000000 --- a/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ /dev/null @@ -1,235 +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.transport.http; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.context.request.async.DeferredResult; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.BasicAdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.BasicTransportToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.QuotaService; -import org.thingsboard.server.common.transport.quota.host.HostRequestsQuotaService; -import org.thingsboard.server.transport.http.session.HttpSessionCtx; - -import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -/** - * @author Andrew Shvayka - */ -@RestController -@RequestMapping("/api/v1") -@Slf4j -public class DeviceApiController { - - @Value("${http.request_timeout}") - private long defaultTimeout; - - @Autowired(required = false) - private SessionMsgProcessor processor; - - @Autowired(required = false) - private DeviceAuthService authService; - - @Autowired(required = false) - private HostRequestsQuotaService quotaService; - - @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") - public DeferredResult getDeviceAttributes(@PathVariable("deviceToken") String deviceToken, - @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, - @RequestParam(value = "sharedKeys", required = false, defaultValue = "") String sharedKeys, - HttpServletRequest httpRequest) { - DeferredResult responseWriter = new DeferredResult(); - if (quotaExceeded(httpRequest, responseWriter)) { - return responseWriter; - } - HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); - if (ctx.login(new DeviceTokenCredentials(deviceToken))) { - GetAttributesRequest request; - if (StringUtils.isEmpty(clientKeys) && StringUtils.isEmpty(sharedKeys)) { - request = new BasicGetAttributesRequest(0); - } else { - Set clientKeySet = !StringUtils.isEmpty(clientKeys) ? new HashSet<>(Arrays.asList(clientKeys.split(","))) : null; - Set sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null; - request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet); - } - process(ctx, request); - } else { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); - } - - return responseWriter; - } - - @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.POST) - public DeferredResult postDeviceAttributes(@PathVariable("deviceToken") String deviceToken, - @RequestBody String json, HttpServletRequest request) { - DeferredResult responseWriter = new DeferredResult(); - if (quotaExceeded(request, responseWriter)) { - return responseWriter; - } - HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); - if (ctx.login(new DeviceTokenCredentials(deviceToken))) { - try { - process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json))); - } catch (IllegalStateException | JsonSyntaxException ex) { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); - } - } else { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); - } - return responseWriter; - } - - @RequestMapping(value = "/{deviceToken}/telemetry", method = RequestMethod.POST) - public DeferredResult postTelemetry(@PathVariable("deviceToken") String deviceToken, - @RequestBody String json, HttpServletRequest request) { - DeferredResult responseWriter = new DeferredResult(); - if (quotaExceeded(request, responseWriter)) { - return responseWriter; - } - HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); - if (ctx.login(new DeviceTokenCredentials(deviceToken))) { - try { - process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json))); - } catch (IllegalStateException | JsonSyntaxException ex) { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); - } - } else { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); - } - return responseWriter; - } - - @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.GET, produces = "application/json") - public DeferredResult subscribeToCommands(@PathVariable("deviceToken") String deviceToken, - @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout, - HttpServletRequest request) { - - return subscribe(deviceToken, timeout, new RpcSubscribeMsg(), request); - } - - @RequestMapping(value = "/{deviceToken}/rpc/{requestId}", method = RequestMethod.POST) - public DeferredResult replyToCommand(@PathVariable("deviceToken") String deviceToken, - @PathVariable("requestId") Integer requestId, - @RequestBody String json, HttpServletRequest request) { - DeferredResult responseWriter = new DeferredResult(); - if (quotaExceeded(request, responseWriter)) { - return responseWriter; - } - HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); - if (ctx.login(new DeviceTokenCredentials(deviceToken))) { - try { - JsonObject response = new JsonParser().parse(json).getAsJsonObject(); - process(ctx, new ToDeviceRpcResponseMsg(requestId, response.toString())); - } catch (IllegalStateException | JsonSyntaxException ex) { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); - } - } else { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); - } - return responseWriter; - } - - @RequestMapping(value = "/{deviceToken}/rpc", method = RequestMethod.POST) - public DeferredResult postRpcRequest(@PathVariable("deviceToken") String deviceToken, - @RequestBody String json, HttpServletRequest httpRequest) { - DeferredResult responseWriter = new DeferredResult(); - if (quotaExceeded(httpRequest, responseWriter)) { - return responseWriter; - } - HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); - if (ctx.login(new DeviceTokenCredentials(deviceToken))) { - try { - JsonObject request = new JsonParser().parse(json).getAsJsonObject(); - process(ctx, new ToServerRpcRequestMsg(0, - request.get("method").getAsString(), - request.get("params").toString())); - } catch (IllegalStateException | JsonSyntaxException ex) { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); - } - } else { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); - } - return responseWriter; - } - - @RequestMapping(value = "/{deviceToken}/attributes/updates", method = RequestMethod.GET, produces = "application/json") - public DeferredResult subscribeToAttributes(@PathVariable("deviceToken") String deviceToken, - @RequestParam(value = "timeout", required = false, defaultValue = "0") long timeout, - HttpServletRequest httpRequest) { - - return subscribe(deviceToken, timeout, new AttributesSubscribeMsg(), httpRequest); - } - - private DeferredResult subscribe(String deviceToken, long timeout, FromDeviceMsg msg, HttpServletRequest httpRequest) { - DeferredResult responseWriter = new DeferredResult(); - if (quotaExceeded(httpRequest, responseWriter)) { - return responseWriter; - } - HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout); - if (ctx.login(new DeviceTokenCredentials(deviceToken))) { - try { - process(ctx, msg); - } catch (IllegalStateException | JsonSyntaxException ex) { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); - } - } else { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); - } - return responseWriter; - } - - private HttpSessionCtx getHttpSessionCtx(DeferredResult responseWriter) { - return getHttpSessionCtx(responseWriter, defaultTimeout); - } - - private HttpSessionCtx getHttpSessionCtx(DeferredResult responseWriter, long timeout) { - return new HttpSessionCtx(processor, authService, responseWriter, timeout != 0 ? timeout : defaultTimeout); - } - - private void process(HttpSessionCtx ctx, FromDeviceMsg request) { - AdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ctx, request); - processor.process(new BasicTransportToDeviceSessionActorMsg(ctx.getDevice(), msg)); - } - - private boolean quotaExceeded(HttpServletRequest request, DeferredResult responseWriter) { - if (quotaService.isQuotaExceeded(request.getRemoteAddr())) { - log.warn("REST Quota exceeded for [{}] . Disconnect", request.getRemoteAddr()); - responseWriter.setResult(new ResponseEntity<>(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)); - return true; - } - return false; - } - -} diff --git a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java b/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java deleted file mode 100644 index 4732785de2..0000000000 --- a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java +++ /dev/null @@ -1,164 +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.transport.http.session; - -import com.google.gson.JsonObject; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.context.request.async.DeferredResult; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.common.msg.session.ex.SessionException; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; - -import java.util.Optional; -import java.util.function.Consumer; - -/** - * @author Andrew Shvayka - */ -@Slf4j -public class HttpSessionCtx extends DeviceAwareSessionContext { - - private final SessionId sessionId; - private final long timeout; - private final DeferredResult responseWriter; - - public HttpSessionCtx(SessionMsgProcessor processor, DeviceAuthService authService, DeferredResult responseWriter, long timeout) { - super(processor, authService); - this.sessionId = new HttpSessionId(); - this.responseWriter = responseWriter; - this.timeout = timeout; - } - - @Override - public SessionType getSessionType() { - return SessionType.SYNC; - } - - @Override - public void onMsg(SessionActorToAdaptorMsg source) throws SessionException { - ToDeviceMsg msg = source.getMsg(); - switch (msg.getSessionMsgType()) { - case GET_ATTRIBUTES_RESPONSE: - reply((GetAttributesResponse) msg); - return; - case STATUS_CODE_RESPONSE: - reply((StatusCodeResponse) msg); - return; - case ATTRIBUTES_UPDATE_NOTIFICATION: - reply((AttributesUpdateNotification) msg); - return; - case TO_DEVICE_RPC_REQUEST: - reply((ToDeviceRpcRequestMsg) msg); - return; - case TO_SERVER_RPC_RESPONSE: - reply((ToServerRpcResponseMsg) msg); - return; - case RULE_ENGINE_ERROR: - reply((RuleEngineErrorMsg) msg); - return; - default: - break; - } - } - - private void reply(RuleEngineErrorMsg msg) { - HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; - switch (msg.getError()) { - case QUEUE_PUT_TIMEOUT: - status = HttpStatus.REQUEST_TIMEOUT; - break; - default: - if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) { - status = HttpStatus.BAD_REQUEST; - } - break; - } - responseWriter.setResult(new ResponseEntity<>(JsonConverter.toErrorJson(msg.getErrorMsg()).toString(), status)); - } - - private void reply(ResponseMsg msg, Consumer f) { - Optional msgError = msg.getError(); - if (!msgError.isPresent()) { - Optional msgData = msg.getData(); - if (msgData.isPresent()) { - f.accept(msgData.get()); - } - } else { - Exception e = msgError.get(); - responseWriter.setResult(new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR)); - } - } - - private void reply(ToDeviceRpcRequestMsg msg) { - responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK)); - } - - private void reply(ToServerRpcResponseMsg msg) { - responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); - } - - private void reply(AttributesUpdateNotification msg) { - responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg.getData(), false).toString(), HttpStatus.OK)); - } - - private void reply(GetAttributesResponse msg) { - reply(msg, payload -> { - if (payload.getClientAttributes().isEmpty() && payload.getSharedAttributes().isEmpty()) { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND)); - } else { - JsonObject result = JsonConverter.toJson(payload, false); - responseWriter.setResult(new ResponseEntity<>(result.toString(), HttpStatus.OK)); - } - }); - } - - private void reply(StatusCodeResponse msg) { - reply(msg, payload -> { - if (payload == 0) { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK)); - } else { - responseWriter.setResult(new ResponseEntity<>(HttpStatus.valueOf(payload))); - } - }); - } - - @Override - public void onMsg(SessionCtrlMsg msg) throws SessionException { - //Do nothing - } - - @Override - public boolean isClosed() { - return false; - } - - @Override - public long getTimeout() { - return timeout; - } - - @Override - public SessionId getSessionId() { - return sessionId; - } -} diff --git a/transport/http/src/main/resources/logback.xml b/transport/http/src/main/resources/logback.xml new file mode 100644 index 0000000000..18864a9fab --- /dev/null +++ b/transport/http/src/main/resources/logback.xml @@ -0,0 +1,34 @@ + + + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml new file mode 100644 index 0000000000..65a1ad93a0 --- /dev/null +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -0,0 +1,69 @@ +# +# 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. +# + +server: + # Server bind address + address: "${HTTP_BIND_ADDRESS:0.0.0.0}" + # Server bind port + port: "${HTTP_BIND_PORT:8081}" + +# HTTP server parameters +transport: + http: + request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" + +#Quota parameters +quota: + host: + # Max allowed number of API requests in interval for single host + limit: "${QUOTA_HOST_LIMIT:10000}" + # Interval duration + intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}" + # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs + ttlMs: "${QUOTA_HOST_TTL_MS:60000}" + # Interval for scheduled task that cleans expired records. TTL is used for expiring + cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}" + # Enable Host API Limits + enabled: "${QUOTA_HOST_ENABLED:true}" + # Array of whitelist hosts + whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}" + # Array of blacklist hosts + blacklist: "${QUOTA_HOST_BLACKLIST:}" + log: + topSize: 10 + intervalMin: 2 + +kafka: + enabled: true + bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" + acks: "${TB_KAFKA_ACKS:all}" + retries: "${TB_KAFKA_RETRIES:1}" + batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" + linger.ms: "${TB_KAFKA_LINGER_MS:1}" + buffer.memory: "${TB_BUFFER_MEMORY:33554432}" + transport_api: + requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" + responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" + max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" + max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" + response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" + rule_engine: + topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}" + notifications: + topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" + poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" + auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" diff --git a/transport/http/src/main/scripts/control/deb/postinst b/transport/http/src/main/scripts/control/deb/postinst new file mode 100644 index 0000000000..d4066c027b --- /dev/null +++ b/transport/http/src/main/scripts/control/deb/postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} +update-rc.d ${pkg.name} defaults + diff --git a/transport/http/src/main/scripts/control/deb/postrm b/transport/http/src/main/scripts/control/deb/postrm new file mode 100644 index 0000000000..61865803c3 --- /dev/null +++ b/transport/http/src/main/scripts/control/deb/postrm @@ -0,0 +1,3 @@ +#!/bin/sh + +update-rc.d -f ${pkg.name} remove diff --git a/transport/http/src/main/scripts/control/deb/preinst b/transport/http/src/main/scripts/control/deb/preinst new file mode 100644 index 0000000000..6be5959285 --- /dev/null +++ b/transport/http/src/main/scripts/control/deb/preinst @@ -0,0 +1,18 @@ +#!/bin/sh + +if ! getent group ${pkg.name} >/dev/null; then + addgroup --system ${pkg.name} +fi + +if ! getent passwd ${pkg.name} >/dev/null; then + adduser --quiet \ + --system \ + --ingroup ${pkg.name} \ + --quiet \ + --disabled-login \ + --disabled-password \ + --home ${pkg.installFolder} \ + --no-create-home \ + -gecos "Thingsboard application" \ + ${pkg.name} +fi diff --git a/transport/http/src/main/scripts/control/deb/prerm b/transport/http/src/main/scripts/control/deb/prerm new file mode 100644 index 0000000000..898d3efd5c --- /dev/null +++ b/transport/http/src/main/scripts/control/deb/prerm @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then + service ${pkg.name} stop +fi diff --git a/transport/http/src/main/scripts/control/rpm/postinst b/transport/http/src/main/scripts/control/rpm/postinst new file mode 100644 index 0000000000..8a7a88f7e0 --- /dev/null +++ b/transport/http/src/main/scripts/control/rpm/postinst @@ -0,0 +1,9 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} + +if [ $1 -eq 1 ] ; then + # Initial installation + systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/http/src/main/scripts/control/rpm/postrm b/transport/http/src/main/scripts/control/rpm/postrm new file mode 100644 index 0000000000..8e1f8a2048 --- /dev/null +++ b/transport/http/src/main/scripts/control/rpm/postrm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -ge 1 ] ; then + # Package upgrade, not uninstall + systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/http/src/main/scripts/control/rpm/preinst b/transport/http/src/main/scripts/control/rpm/preinst new file mode 100644 index 0000000000..e19fc884c8 --- /dev/null +++ b/transport/http/src/main/scripts/control/rpm/preinst @@ -0,0 +1,6 @@ +#!/bin/sh + +getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} +getent passwd ${pkg.name} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +-c "Thingsboard application" diff --git a/transport/http/src/main/scripts/control/rpm/prerm b/transport/http/src/main/scripts/control/rpm/prerm new file mode 100644 index 0000000000..accb487b8e --- /dev/null +++ b/transport/http/src/main/scripts/control/rpm/prerm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : +fi diff --git a/transport/http/src/main/scripts/control/tb-http-transport.service b/transport/http/src/main/scripts/control/tb-http-transport.service new file mode 100644 index 0000000000..d456fc03c0 --- /dev/null +++ b/transport/http/src/main/scripts/control/tb-http-transport.service @@ -0,0 +1,11 @@ +[Unit] +Description=${pkg.name} +After=syslog.target + +[Service] +User=${pkg.name} +ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target diff --git a/transport/http/src/main/scripts/windows/install.bat b/transport/http/src/main/scripts/windows/install.bat new file mode 100644 index 0000000000..dba773663c --- /dev/null +++ b/transport/http/src/main/scripts/windows/install.bat @@ -0,0 +1,87 @@ +@ECHO OFF + +setlocal ENABLEEXTENSIONS + +@ECHO Detecting Java version installed. +:CHECK_JAVA_64 +@ECHO Detecting if it is 64 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED + +:CHECK_JAVA_32 +@ECHO Detecting if it is 32 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED + +:JAVA_INSTALLED + +@ECHO Java 1.8 found! +@ECHO Installing ${pkg.name} ... + +%BASE%${pkg.name}.exe install + +@ECHO ${pkg.name} installed successfully! + +GOTO END + +:JAVA_NOT_INSTALLED +@ECHO Java 1.8 or above is not installed +@ECHO Please go to https://java.com/ and install Java. Then retry installation. +PAUSE +GOTO END + +:END + + diff --git a/transport/http/src/main/scripts/windows/service.xml b/transport/http/src/main/scripts/windows/service.xml new file mode 100644 index 0000000000..f7b9d307c8 --- /dev/null +++ b/transport/http/src/main/scripts/windows/service.xml @@ -0,0 +1,36 @@ + + ${pkg.name} + ${project.name} + ${project.description} + %BASE%\conf + ${pkg.winWrapperLogFolder} + rotate + + java + -Xloggc:%BASE%\logs\gc.log + -XX:+HeapDumpOnOutOfMemoryError + -XX:+PrintGCDetails + -XX:+PrintGCDateStamps + -XX:+PrintHeapAtGC + -XX:+PrintTenuringDistribution + -XX:+PrintGCApplicationStoppedTime + -XX:+UseGCLogFileRotation + -XX:NumberOfGCLogFiles=10 + -XX:GCLogFileSize=10M + -XX:-UseBiasedLocking + -XX:+UseTLAB + -XX:+ResizeTLAB + -XX:+PerfDisableSharedMem + -XX:+UseCondCardMark + -XX:CMSWaitDuration=10000 + -XX:+UseParNewGC + -XX:+UseConcMarkSweepGC + -XX:+CMSParallelRemarkEnabled + -XX:+CMSParallelInitialMarkEnabled + -XX:+CMSEdenChunksRecordAlways + -XX:CMSInitiatingOccupancyFraction=75 + -XX:+UseCMSInitiatingOccupancyOnly + -jar + %BASE%\lib\${pkg.name}.jar + + diff --git a/transport/http/src/main/scripts/windows/uninstall.bat b/transport/http/src/main/scripts/windows/uninstall.bat new file mode 100644 index 0000000000..921e4c8395 --- /dev/null +++ b/transport/http/src/main/scripts/windows/uninstall.bat @@ -0,0 +1,9 @@ +@ECHO OFF + +@ECHO Stopping ${pkg.name} ... +net stop ${pkg.name} + +@ECHO Uninstalling ${pkg.name} ... +%~dp0${pkg.name}.exe uninstall + +@ECHO DONE. \ No newline at end of file diff --git a/transport/mqtt/build.gradle b/transport/mqtt/build.gradle new file mode 100644 index 0000000000..6d54cb4789 --- /dev/null +++ b/transport/mqtt/build.gradle @@ -0,0 +1,140 @@ +/** + * 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. + */ +import org.apache.tools.ant.filters.ReplaceTokens + +buildscript { + ext { + osPackageVersion = "3.8.0" + } + repositories { + jcenter() + } + dependencies { + classpath("com.netflix.nebula:gradle-ospackage-plugin:${osPackageVersion}") + } +} + +apply plugin: "nebula.ospackage" + +buildDir = projectBuildDir +version = projectVersion +distsDirName = "./" + +// OS Package plugin configuration +ospackage { + packageName = pkgName + version = "${project.version}" + release = 1 + os = LINUX + type = BINARY + + into pkgInstallFolder + + user pkgName + permissionGroup pkgName + + // Copy the actual .jar file + from(mainJar) { + // Strip the version from the jar filename + rename { String fileName -> + "${pkgName}.jar" + } + fileMode 0500 + into "bin" + } + + // Copy the config files + from("target/conf") { + exclude "${pkgName}.conf" + fileType CONFIG | NOREPLACE + fileMode 0754 + into "conf" + } + +} + +// Configure our RPM build task +buildRpm { + + arch = NOARCH + + version = projectVersion.replace('-', '') + archiveName = "${pkgName}.rpm" + + requires("java-1.8.0") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'rpm']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + preInstall file("${buildDir}/control/rpm/preinst") + postInstall file("${buildDir}/control/rpm/postinst") + preUninstall file("${buildDir}/control/rpm/prerm") + postUninstall file("${buildDir}/control/rpm/postrm") + + user pkgName + permissionGroup pkgName + + // Copy the system unit files + from("${buildDir}/control/${pkgName}.service") { + addParentDirs = false + fileMode 0644 + into "/usr/lib/systemd/system" + } + + directory(pkgLogFolder, 0755) + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} + +// Same as the buildRpm task +buildDeb { + + arch = "all" + + archiveName = "${pkgName}.deb" + + requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless") + + from("target/conf") { + include "${pkgName}.conf" + filter(ReplaceTokens, tokens: ['pkg.platform': 'deb']) + fileType CONFIG | NOREPLACE + fileMode 0754 + into "${pkgInstallFolder}/conf" + } + + configurationFile("${pkgInstallFolder}/conf/${pkgName}.conf") + configurationFile("${pkgInstallFolder}/conf/${pkgName}.yml") + configurationFile("${pkgInstallFolder}/conf/logback.xml") + + preInstall file("${buildDir}/control/deb/preinst") + postInstall file("${buildDir}/control/deb/postinst") + preUninstall file("${buildDir}/control/deb/prerm") + postUninstall file("${buildDir}/control/deb/postrm") + + user pkgName + permissionGroup pkgName + + directory(pkgLogFolder, 0755) + link("/etc/init.d/${pkgName}", "${pkgInstallFolder}/bin/${pkgName}.jar") + link("${pkgInstallFolder}/bin/${pkgName}.yml", "${pkgInstallFolder}/conf/${pkgName}.yml") + link("/etc/${pkgName}/conf", "${pkgInstallFolder}/conf") +} diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 785e9a5306..6395b40365 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -27,46 +27,37 @@ mqtt jar - Thingsboard MQTT Transport + Thingsboard MQTT Transport Service https://thingsboard.io UTF-8 ${basedir}/../.. + tb-mqtt-transport + /var/log/${pkg.name} + /usr/share/${pkg.name} + ${project.build.directory}/windows - org.thingsboard.common - transport - - - io.netty - netty-all + org.thingsboard.common.transport + mqtt - org.springframework - spring-context - - - org.slf4j - slf4j-api - - - org.slf4j - log4j-over-slf4j - - - ch.qos.logback - logback-core + org.thingsboard.common + queue - ch.qos.logback - logback-classic + org.springframework.boot + spring-boot-starter-web - com.google.guava - guava + com.sun.winsw + winsw + bin + exe + provided org.springframework.boot @@ -85,4 +76,266 @@ + + ${pkg.name}-${project.version} + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/resources + + logback.xml + + false + + + + + + copy-service-conf + process-resources + + copy-resources + + + ${project.build.directory}/conf + + + src/main/conf + true + + + + src/main/filters/unix.properties + + + + + copy-win-conf + process-resources + + copy-resources + + + ${pkg.win.dist}/conf + + + src/main/resources + + logback.xml + + false + + + src/main/conf + + tb-mqtt-transport.conf + + true + + + + src/main/filters/windows.properties + + + + + copy-control + process-resources + + copy-resources + + + ${project.build.directory}/control + + + src/main/scripts/control + true + + + + src/main/filters/unix.properties + + + + + copy-windows-control + process-resources + + copy-resources + + + ${pkg.win.dist} + + + src/main/scripts/windows + true + + + + src/main/filters/windows.properties + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-winsw-service + package + + copy + + + + + com.sun.winsw + winsw + bin + exe + service.exe + + + ${pkg.win.dist} + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + **/logback.xml + + + + ThingsBoard MQTT Transport Service + ${project.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin + + org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication + boot + ZIP + true + true + + ${pkg.installFolder}/conf + ${pkg.unixLogFolder} + ${pkg.name}.out + ${pkg.name} + + + + + + repackage + + + + + + org.fortasoft + gradle-maven-plugin + + + build + buildDeb + buildRpm + + + -PprojectBuildDir=${project.build.directory} + -PprojectVersion=${project.version} + -PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + -PpkgName=${pkg.name} + -PpkgInstallFolder=${pkg.installFolder} + -PpkgLogFolder=${pkg.unixLogFolder} + + + + + package + + invoke + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + ${pkg.name} + + src/main/assembly/windows.xml + + + + + assembly + package + + single + + + + + + org.apache.maven.plugins + maven-install-plugin + + ${project.build.directory}/${pkg.name}.deb + ${project.artifactId} + ${project.groupId} + ${project.version} + deb + deb + + + + install-deb + package + + install-file + + + + + + + + + jenkins + Jenkins Repository + http://repo.jenkins-ci.org/releases + + false + + + diff --git a/transport/mqtt/src/main/assembly/windows.xml b/transport/mqtt/src/main/assembly/windows.xml new file mode 100644 index 0000000000..82da34ed13 --- /dev/null +++ b/transport/mqtt/src/main/assembly/windows.xml @@ -0,0 +1,71 @@ + + + windows + + + zip + + + + + + ${pkg.win.dist} + logs + + */** + + + + ${pkg.win.dist}/conf + conf + windows + + + + + + ${project.build.directory}/${project.build.finalName}-boot.${project.packaging} + lib + ${pkg.name}.jar + + + ${pkg.win.dist}/service.exe + + ${pkg.name}.exe + + + ${pkg.win.dist}/service.xml + + ${pkg.name}.xml + windows + + + ${pkg.win.dist}/install.bat + + windows + + + ${pkg.win.dist}/uninstall.bat + + windows + + + diff --git a/transport/mqtt/src/main/conf/logback.xml b/transport/mqtt/src/main/conf/logback.xml new file mode 100644 index 0000000000..f36469d5f2 --- /dev/null +++ b/transport/mqtt/src/main/conf/logback.xml @@ -0,0 +1,43 @@ + + + + + + + ${pkg.logFolder}/${pkg.name}.log + + ${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/transport/mqtt/src/main/conf/tb-mqtt-transport.conf b/transport/mqtt/src/main/conf/tb-mqtt-transport.conf new file mode 100644 index 0000000000..0afa91c18e --- /dev/null +++ b/transport/mqtt/src/main/conf/tb-mqtt-transport.conf @@ -0,0 +1,23 @@ +# +# 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" +export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" +export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/mqtt/src/main/filters/unix.properties b/transport/mqtt/src/main/filters/unix.properties new file mode 100644 index 0000000000..8967278673 --- /dev/null +++ b/transport/mqtt/src/main/filters/unix.properties @@ -0,0 +1 @@ +pkg.logFolder=${pkg.unixLogFolder} \ No newline at end of file diff --git a/transport/mqtt/src/main/filters/windows.properties b/transport/mqtt/src/main/filters/windows.properties new file mode 100644 index 0000000000..a6e48d91ba --- /dev/null +++ b/transport/mqtt/src/main/filters/windows.properties @@ -0,0 +1,2 @@ +pkg.logFolder=${BASE}\\logs +pkg.winWrapperLogFolder=%BASE%\\logs diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java b/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java new file mode 100644 index 0000000000..2bae2ff855 --- /dev/null +++ b/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java @@ -0,0 +1,49 @@ +/** + * 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.mqtt; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.Arrays; + +@SpringBootConfiguration +@EnableAsync +@EnableScheduling +@ComponentScan({"org.thingsboard.server.mqtt", "org.thingsboard.server.common", "org.thingsboard.server.transport.mqtt", "org.thingsboard.server.kafka"}) +public class ThingsboardMqttTransportApplication { + + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-mqtt-transport"; + + public static void main(String[] args) { + SpringApplication.run(ThingsboardMqttTransportApplication.class, updateArguments(args)); + } + + private static String[] updateArguments(String[] args) { + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { + String[] modifiedArgs = new String[args.length + 1]; + System.arraycopy(args, 0, modifiedArgs, 0, args.length); + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; + return modifiedArgs; + } + return args; + } +} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java deleted file mode 100644 index 3c8365e27d..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ /dev/null @@ -1,456 +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.transport.mqtt; - -import com.fasterxml.jackson.databind.JsonNode; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.handler.codec.mqtt.*; -import io.netty.handler.codec.mqtt.MqttConnectReturnCode; -import io.netty.handler.codec.mqtt.MqttQoS; -import io.netty.handler.ssl.SslHandler; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.data.security.DeviceX509Credentials; -import org.thingsboard.server.common.msg.core.SessionOpenMsg; -import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.BasicAdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.BasicTransportToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.QuotaService; -import org.thingsboard.server.dao.EncryptionUtil; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; -import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; -import org.thingsboard.server.transport.mqtt.session.GatewaySessionCtx; -import org.thingsboard.server.transport.mqtt.util.SslUtil; - -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.security.cert.X509Certificate; -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.regex.Pattern; - -import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*; -import static io.netty.handler.codec.mqtt.MqttMessageType.*; -import static io.netty.handler.codec.mqtt.MqttQoS.*; -import static org.thingsboard.server.common.msg.session.SessionMsgType.*; -import static org.thingsboard.server.transport.mqtt.MqttTopics.*; - -/** - * @author Andrew Shvayka - */ -@Slf4j -public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener> { - - public static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE; - - private final DeviceSessionCtx deviceSessionCtx; - private final String sessionId; - private final MqttTransportAdaptor adaptor; - private final SessionMsgProcessor processor; - private final DeviceService deviceService; - private final DeviceAuthService authService; - private final RelationService relationService; - private final QuotaService quotaService; - private final SslHandler sslHandler; - private final ConcurrentMap mqttQoSMap; - - private volatile boolean connected; - private volatile InetSocketAddress address; - private volatile GatewaySessionCtx gatewaySessionCtx; - - public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, - MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService) { - this.processor = processor; - this.deviceService = deviceService; - this.relationService = relationService; - this.authService = authService; - this.adaptor = adaptor; - this.mqttQoSMap = new ConcurrentHashMap<>(); - this.deviceSessionCtx = new DeviceSessionCtx(processor, authService, adaptor, mqttQoSMap); - this.sessionId = deviceSessionCtx.getSessionId().toUidStr(); - this.sslHandler = sslHandler; - this.quotaService = quotaService; - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - log.trace("[{}] Processing msg: {}", sessionId, msg); - if (msg instanceof MqttMessage) { - processMqttMsg(ctx, (MqttMessage) msg); - } else { - ctx.close(); - } - } - - private void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) { - address = (InetSocketAddress) ctx.channel().remoteAddress(); - if (msg.fixedHeader() == null) { - log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort()); - processDisconnect(ctx); - return; - } - - if (quotaService.isQuotaExceeded(address.getHostName())) { - log.warn("MQTT Quota exceeded for [{}:{}] . Disconnect", address.getHostName(), address.getPort()); - processDisconnect(ctx); - return; - } - - deviceSessionCtx.setChannel(ctx); - switch (msg.fixedHeader().messageType()) { - case CONNECT: - processConnect(ctx, (MqttConnectMessage) msg); - break; - case PUBLISH: - processPublish(ctx, (MqttPublishMessage) msg); - break; - case SUBSCRIBE: - processSubscribe(ctx, (MqttSubscribeMessage) msg); - break; - case UNSUBSCRIBE: - processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg); - break; - case PINGREQ: - if (checkConnected(ctx)) { - ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); - } - break; - case DISCONNECT: - if (checkConnected(ctx)) { - processDisconnect(ctx); - } - break; - default: - break; - } - - } - - private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) { - if (!checkConnected(ctx)) { - return; - } - String topicName = mqttMsg.variableHeader().topicName(); - int msgId = mqttMsg.variableHeader().messageId(); - log.trace("[{}] Processing publish msg [{}][{}]!", sessionId, topicName, msgId); - - if (topicName.startsWith(BASE_GATEWAY_API_TOPIC)) { - if (gatewaySessionCtx != null) { - gatewaySessionCtx.setChannel(ctx); - handleMqttPublishMsg(topicName, msgId, mqttMsg); - } - } else { - processDevicePublish(ctx, mqttMsg, topicName, msgId); - } - } - - private void handleMqttPublishMsg(String topicName, int msgId, MqttPublishMessage mqttMsg) { - try { - switch (topicName) { - case GATEWAY_TELEMETRY_TOPIC: - gatewaySessionCtx.onDeviceTelemetry(mqttMsg); - break; - case GATEWAY_ATTRIBUTES_TOPIC: - gatewaySessionCtx.onDeviceAttributes(mqttMsg); - break; - case GATEWAY_ATTRIBUTES_REQUEST_TOPIC: - gatewaySessionCtx.onDeviceAttributesRequest(mqttMsg); - break; - case GATEWAY_RPC_TOPIC: - gatewaySessionCtx.onDeviceRpcResponse(mqttMsg); - break; - case GATEWAY_CONNECT_TOPIC: - gatewaySessionCtx.onDeviceConnect(mqttMsg); - break; - case GATEWAY_DISCONNECT_TOPIC: - gatewaySessionCtx.onDeviceDisconnect(mqttMsg); - break; - } - } catch (RuntimeException | AdaptorException e) { - log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); - } - } - - private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { - AdaptorToSessionActorMsg msg = null; - try { - if (topicName.equals(DEVICE_TELEMETRY_TOPIC)) { - msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_TELEMETRY_REQUEST, mqttMsg); - } else if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) { - msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_ATTRIBUTES_REQUEST, mqttMsg); - } else if (topicName.startsWith(DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { - msg = adaptor.convertToActorMsg(deviceSessionCtx, GET_ATTRIBUTES_REQUEST, mqttMsg); - if (msgId >= 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } - } else if (topicName.startsWith(DEVICE_RPC_RESPONSE_TOPIC)) { - msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_DEVICE_RPC_RESPONSE, mqttMsg); - if (msgId >= 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } - } else if (topicName.startsWith(DEVICE_RPC_REQUESTS_TOPIC)) { - msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_SERVER_RPC_REQUEST, mqttMsg); - if (msgId >= 0) { - ctx.writeAndFlush(createMqttPubAckMsg(msgId)); - } - } - } catch (AdaptorException e) { - log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); - } - if (msg != null) { - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg)); - } else { - log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId); - ctx.close(); - } - } - - private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { - if (!checkConnected(ctx)) { - return; - } - log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); - List grantedQoSList = new ArrayList<>(); - for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) { - String topic = subscription.topicName(); - MqttQoS reqQoS = subscription.qualityOfService(); - try { - switch (topic) { - case DEVICE_ATTRIBUTES_TOPIC: { - AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, SUBSCRIBE_ATTRIBUTES_REQUEST, mqttMsg); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg)); - registerSubQoS(topic, grantedQoSList, reqQoS); - break; - } - case DEVICE_RPC_REQUESTS_SUB_TOPIC: { - AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, SUBSCRIBE_RPC_COMMANDS_REQUEST, mqttMsg); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg)); - registerSubQoS(topic, grantedQoSList, reqQoS); - break; - } - case DEVICE_RPC_RESPONSE_SUB_TOPIC: - case GATEWAY_ATTRIBUTES_TOPIC: - case GATEWAY_RPC_TOPIC: - registerSubQoS(topic, grantedQoSList, reqQoS); - break; - case DEVICE_ATTRIBUTES_RESPONSES_TOPIC: - deviceSessionCtx.setAllowAttributeResponses(); - registerSubQoS(topic, grantedQoSList, reqQoS); - break; - default: - log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); - grantedQoSList.add(FAILURE.value()); - break; - } - } catch (AdaptorException e) { - log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); - grantedQoSList.add(FAILURE.value()); - } - } - ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList)); - } - - private void registerSubQoS(String topic, List grantedQoSList, MqttQoS reqQoS) { - grantedQoSList.add(getMinSupportedQos(reqQoS)); - mqttQoSMap.put(new MqttTopicMatcher(topic), getMinSupportedQos(reqQoS)); - } - - private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { - if (!checkConnected(ctx)) { - return; - } - log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); - for (String topicName : mqttMsg.payload().topics()) { - mqttQoSMap.remove(new MqttTopicMatcher(topicName)); - try { - switch (topicName) { - case DEVICE_ATTRIBUTES_TOPIC: { - AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, UNSUBSCRIBE_ATTRIBUTES_REQUEST, mqttMsg); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg)); - break; - } - case DEVICE_RPC_REQUESTS_SUB_TOPIC: { - AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, mqttMsg); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg)); - break; - } - case DEVICE_ATTRIBUTES_RESPONSES_TOPIC: - deviceSessionCtx.setDisallowAttributeResponses(); - break; - } - } catch (AdaptorException e) { - log.warn("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName); - } - } - ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId())); - } - - private MqttMessage createUnSubAckMessage(int msgId) { - MqttFixedHeader mqttFixedHeader = - new MqttFixedHeader(UNSUBACK, false, AT_LEAST_ONCE, false, 0); - MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); - return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader); - } - - private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { - log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); - X509Certificate cert; - if (sslHandler != null && (cert = getX509Certificate()) != null) { - processX509CertConnect(ctx, cert); - } else { - processAuthTokenConnect(ctx, msg); - } - } - - private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { - String userName = msg.payload().userName(); - if (StringUtils.isEmpty(userName)) { - ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)); - ctx.close(); - } else if (!deviceSessionCtx.login(new DeviceTokenCredentials(msg.payload().userName()))) { - ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); - ctx.close(); - } else { - ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); - connected = true; - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), - new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new SessionOpenMsg()))); - checkGatewaySession(); - } - } - - private void processX509CertConnect(ChannelHandlerContext ctx, X509Certificate cert) { - try { - String strCert = SslUtil.getX509CertificateString(cert); - String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - if (deviceSessionCtx.login(new DeviceX509Credentials(sha3Hash))) { - ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); - connected = true; - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), - new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new SessionOpenMsg()))); - checkGatewaySession(); - } else { - ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); - ctx.close(); - } - } catch (Exception e) { - ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); - ctx.close(); - } - } - - private X509Certificate getX509Certificate() { - try { - X509Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificateChain(); - if (certChain.length > 0) { - return certChain[0]; - } - } catch (SSLPeerUnverifiedException e) { - log.warn(e.getMessage()); - return null; - } - return null; - } - - private void processDisconnect(ChannelHandlerContext ctx) { - ctx.close(); - if (connected) { - processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId())); - if (gatewaySessionCtx != null) { - gatewaySessionCtx.onGatewayDisconnect(); - } - } - } - - private MqttConnAckMessage createMqttConnAckMsg(MqttConnectReturnCode returnCode) { - MqttFixedHeader mqttFixedHeader = - new MqttFixedHeader(CONNACK, false, AT_MOST_ONCE, false, 0); - MqttConnAckVariableHeader mqttConnAckVariableHeader = - new MqttConnAckVariableHeader(returnCode, true); - return new MqttConnAckMessage(mqttFixedHeader, mqttConnAckVariableHeader); - } - - @Override - public void channelReadComplete(ChannelHandlerContext ctx) { - ctx.flush(); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - log.error("[{}] Unexpected Exception", sessionId, cause); - ctx.close(); - } - - private static MqttSubAckMessage createSubAckMessage(Integer msgId, List grantedQoSList) { - MqttFixedHeader mqttFixedHeader = - new MqttFixedHeader(SUBACK, false, AT_LEAST_ONCE, false, 0); - MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); - MqttSubAckPayload mqttSubAckPayload = new MqttSubAckPayload(grantedQoSList); - return new MqttSubAckMessage(mqttFixedHeader, mqttMessageIdVariableHeader, mqttSubAckPayload); - } - - private static int getMinSupportedQos(MqttQoS reqQoS) { - return Math.min(reqQoS.value(), MAX_SUPPORTED_QOS_LVL.value()); - } - - public static MqttPubAckMessage createMqttPubAckMsg(int requestId) { - MqttFixedHeader mqttFixedHeader = - new MqttFixedHeader(PUBACK, false, AT_LEAST_ONCE, false, 0); - MqttMessageIdVariableHeader mqttMsgIdVariableHeader = - MqttMessageIdVariableHeader.from(requestId); - return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader); - } - - private boolean checkConnected(ChannelHandlerContext ctx) { - if (connected) { - return true; - } else { - log.info("[{}] Closing current session due to invalid msg order [{}][{}]", sessionId); - ctx.close(); - return false; - } - } - - private void checkGatewaySession() { - Device device = deviceSessionCtx.getDevice(); - JsonNode infoNode = device.getAdditionalInfo(); - if (infoNode != null) { - JsonNode gatewayNode = infoNode.get("gateway"); - if (gatewayNode != null && gatewayNode.asBoolean()) { - gatewaySessionCtx = new GatewaySessionCtx(processor, deviceService, authService, relationService, deviceSessionCtx); - } - } - } - - @Override - public void operationComplete(Future future) throws Exception { - processor.process(SessionCloseMsg.onError(deviceSessionCtx.getSessionId())); - } -} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java deleted file mode 100644 index 432966eeff..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java +++ /dev/null @@ -1,76 +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.transport.mqtt; - -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.mqtt.MqttDecoder; -import io.netty.handler.codec.mqtt.MqttEncoder; -import io.netty.handler.ssl.SslHandler; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.transport.quota.QuotaService; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; - -/** - * @author Andrew Shvayka - */ -public class MqttTransportServerInitializer extends ChannelInitializer { - - private final SessionMsgProcessor processor; - private final DeviceService deviceService; - private final DeviceAuthService authService; - private final RelationService relationService; - private final MqttTransportAdaptor adaptor; - private final MqttSslHandlerProvider sslHandlerProvider; - private final QuotaService quotaService; - private final int maxPayloadSize; - - public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, - MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider, - QuotaService quotaService, int maxPayloadSize) { - this.processor = processor; - this.deviceService = deviceService; - this.authService = authService; - this.relationService = relationService; - this.adaptor = adaptor; - this.sslHandlerProvider = sslHandlerProvider; - this.quotaService = quotaService; - this.maxPayloadSize = maxPayloadSize; - } - - @Override - public void initChannel(SocketChannel ch) { - ChannelPipeline pipeline = ch.pipeline(); - SslHandler sslHandler = null; - if (sslHandlerProvider != null) { - sslHandler = sslHandlerProvider.getSslHandler(); - pipeline.addLast(sslHandler); - } - pipeline.addLast("decoder", new MqttDecoder(maxPayloadSize)); - pipeline.addLast("encoder", MqttEncoder.INSTANCE); - - MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService, - adaptor, sslHandler, quotaService); - - pipeline.addLast(handler); - ch.closeFuture().addListener(handler); - } - -} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java deleted file mode 100644 index c8baaf7e53..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ /dev/null @@ -1,273 +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.transport.mqtt.adaptors; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.UnpooledByteBufAllocator; -import io.netty.handler.codec.mqtt.*; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.transport.mqtt.MqttTopics; -import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; -import org.thingsboard.server.transport.mqtt.MqttTransportHandler; - -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -/** - * @author Andrew Shvayka - */ -@Component("JsonMqttAdaptor") -@Slf4j -public class JsonMqttAdaptor implements MqttTransportAdaptor { - - private static final Gson GSON = new Gson(); - private static final Charset UTF8 = Charset.forName("UTF-8"); - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); - - @Override - public AdaptorToSessionActorMsg convertToActorMsg(DeviceSessionCtx ctx, SessionMsgType type, MqttMessage inbound) throws AdaptorException { - FromDeviceMsg msg; - switch (type) { - case POST_TELEMETRY_REQUEST: - msg = convertToTelemetryUploadRequest(ctx, (MqttPublishMessage) inbound); - break; - case POST_ATTRIBUTES_REQUEST: - msg = convertToUpdateAttributesRequest(ctx, (MqttPublishMessage) inbound); - break; - case SUBSCRIBE_ATTRIBUTES_REQUEST: - msg = new AttributesSubscribeMsg(); - break; - case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - msg = new AttributesUnsubscribeMsg(); - break; - case SUBSCRIBE_RPC_COMMANDS_REQUEST: - msg = new RpcSubscribeMsg(); - break; - case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - msg = new RpcUnsubscribeMsg(); - break; - case GET_ATTRIBUTES_REQUEST: - msg = convertToGetAttributesRequest(ctx, (MqttPublishMessage) inbound); - break; - case TO_DEVICE_RPC_RESPONSE: - msg = convertToRpcCommandResponse(ctx, (MqttPublishMessage) inbound); - break; - case TO_SERVER_RPC_REQUEST: - msg = convertToServerRpcRequest(ctx, (MqttPublishMessage) inbound); - break; - default: - log.warn("[{}] Unsupported msg type: {}!", ctx.getSessionId(), type); - throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + type + "!")); - } - return new BasicAdaptorToSessionActorMsg(ctx, msg); - } - - @Override - public Optional convertToAdaptorMsg(DeviceSessionCtx ctx, SessionActorToAdaptorMsg sessionMsg) throws AdaptorException { - MqttMessage result = null; - ToDeviceMsg msg = sessionMsg.getMsg(); - switch (msg.getSessionMsgType()) { - case STATUS_CODE_RESPONSE: - case GET_ATTRIBUTES_RESPONSE: - ResponseMsg responseMsg = (ResponseMsg) msg; - Optional responseError = responseMsg.getError(); - if (responseMsg.isSuccess()) { - result = convertResponseMsg(ctx, msg, responseMsg, responseError); - } else { - if (responseError.isPresent()) { - throw new AdaptorException(responseError.get()); - } - } - break; - case ATTRIBUTES_UPDATE_NOTIFICATION: - AttributesUpdateNotification notification = (AttributesUpdateNotification) msg; - result = createMqttPublishMsg(ctx, MqttTopics.DEVICE_ATTRIBUTES_TOPIC, notification.getData(), false); - break; - case TO_DEVICE_RPC_REQUEST: - ToDeviceRpcRequestMsg rpcRequest = (ToDeviceRpcRequestMsg) msg; - result = createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), - rpcRequest); - break; - case TO_SERVER_RPC_RESPONSE: - ToServerRpcResponseMsg rpcResponse = (ToServerRpcResponseMsg) msg; - result = createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC + rpcResponse.getRequestId(), - rpcResponse); - break; - case RULE_ENGINE_ERROR: - RuleEngineErrorMsg errorMsg = (RuleEngineErrorMsg) msg; - result = createMqttPublishMsg(ctx, "errors", JsonConverter.toErrorJson(errorMsg.getErrorMsg())); - break; - default: - break; - } - return Optional.ofNullable(result); - } - - private MqttMessage convertResponseMsg(DeviceSessionCtx ctx, ToDeviceMsg msg, - ResponseMsg responseMsg, Optional responseError) throws AdaptorException { - MqttMessage result = null; - SessionMsgType requestMsgType = responseMsg.getRequestMsgType(); - Integer requestId = responseMsg.getRequestId(); - if (requestId >= 0) { - if (requestMsgType == SessionMsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == SessionMsgType.POST_TELEMETRY_REQUEST) { - result = MqttTransportHandler.createMqttPubAckMsg(requestId); - } else if (requestMsgType == SessionMsgType.GET_ATTRIBUTES_REQUEST) { - GetAttributesResponse response = (GetAttributesResponse) msg; - Optional responseData = response.getData(); - if (response.isSuccess() && responseData.isPresent()) { - result = createMqttPublishMsg(ctx, - MqttTopics.DEVICE_ATTRIBUTES_RESPONSE_TOPIC_PREFIX + requestId, - responseData.get(), true); - } else { - if (responseError.isPresent()) { - throw new AdaptorException(responseError.get()); - } - } - } - } - return result; - } - - private MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, AttributesKVMsg msg, boolean asMap) { - return createMqttPublishMsg(ctx, topic, JsonConverter.toJson(msg, asMap)); - } - - private MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, ToDeviceRpcRequestMsg msg) { - return createMqttPublishMsg(ctx, topic, JsonConverter.toJson(msg, false)); - } - - private MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, ToServerRpcResponseMsg msg) { - return createMqttPublishMsg(ctx, topic, JsonConverter.toJson(msg)); - } - - private MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, JsonElement json) { - MqttFixedHeader mqttFixedHeader = - new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); - MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); - ByteBuf payload = ALLOCATOR.buffer(); - payload.writeBytes(GSON.toJson(json).getBytes(UTF8)); - return new MqttPublishMessage(mqttFixedHeader, header, payload); - } - - private FromDeviceMsg convertToGetAttributesRequest(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException { - String topicName = inbound.variableHeader().topicName(); - try { - Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX.length())); - String payload = inbound.payload().toString(UTF8); - JsonElement requestBody = new JsonParser().parse(payload); - Set clientKeys = toStringSet(requestBody, "clientKeys"); - Set sharedKeys = toStringSet(requestBody, "sharedKeys"); - if (clientKeys == null && sharedKeys == null) { - return new BasicGetAttributesRequest(requestId); - } else { - return new BasicGetAttributesRequest(requestId, clientKeys, sharedKeys); - } - } catch (RuntimeException e) { - log.warn("Failed to decode get attributes request", e); - throw new AdaptorException(e); - } - } - - private FromDeviceMsg convertToRpcCommandResponse(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException { - String topicName = inbound.variableHeader().topicName(); - try { - Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_RESPONSE_TOPIC.length())); - String payload = inbound.payload().toString(UTF8); - return new ToDeviceRpcResponseMsg( - requestId, - payload); - } catch (RuntimeException e) { - log.warn("Failed to decode get attributes request", e); - throw new AdaptorException(e); - } - } - - private Set toStringSet(JsonElement requestBody, String name) { - JsonElement element = requestBody.getAsJsonObject().get(name); - if (element != null) { - return new HashSet<>(Arrays.asList(element.getAsString().split(","))); - } else { - return null; - } - } - - private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { - String payload = validatePayload(ctx.getSessionId(), inbound.payload()); - try { - return JsonConverter.convertToAttributes(new JsonParser().parse(payload), inbound.variableHeader().messageId()); - } catch (IllegalStateException | JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - private TelemetryUploadRequest convertToTelemetryUploadRequest(SessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { - String payload = validatePayload(ctx.getSessionId(), inbound.payload()); - try { - return JsonConverter.convertToTelemetry(new JsonParser().parse(payload), inbound.variableHeader().messageId()); - } catch (IllegalStateException | JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - private FromDeviceMsg convertToServerRpcRequest(DeviceSessionCtx ctx, MqttPublishMessage inbound) throws AdaptorException { - String topicName = inbound.variableHeader().topicName(); - String payload = validatePayload(ctx.getSessionId(), inbound.payload()); - try { - Integer requestId = Integer.valueOf(topicName.substring(MqttTopics.DEVICE_RPC_REQUESTS_TOPIC.length())); - return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId); - } catch (IllegalStateException | JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - public static JsonElement validateJsonPayload(SessionId sessionId, ByteBuf payloadData) throws AdaptorException { - String payload = validatePayload(sessionId, payloadData); - try { - return new JsonParser().parse(payload); - } catch (JsonSyntaxException ex) { - throw new AdaptorException(ex); - } - } - - public static String validatePayload(SessionId sessionId, ByteBuf payloadData) throws AdaptorException { - try { - String payload = payloadData.toString(UTF8); - if (payload == null) { - log.warn("[{}] Payload is empty!", sessionId.toUidStr()); - throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); - } - return payload; - } finally { - payloadData.release(); - } - } - -} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java deleted file mode 100644 index bf83a1fd98..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ /dev/null @@ -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.transport.mqtt.adaptors; - -import io.netty.handler.codec.mqtt.MqttMessage; -import org.thingsboard.server.common.transport.TransportAdaptor; -import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; - -/** - * @author Andrew Shvayka - */ -public interface MqttTransportAdaptor extends TransportAdaptor { -} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java deleted file mode 100644 index 256ed119da..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ /dev/null @@ -1,119 +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.transport.mqtt.session; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.mqtt.MqttFixedHeader; -import io.netty.handler.codec.mqtt.MqttMessage; -import io.netty.handler.codec.mqtt.MqttMessageType; -import io.netty.handler.codec.mqtt.MqttQoS; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; -import org.thingsboard.server.common.msg.session.SessionCtrlMsg; -import org.thingsboard.server.common.msg.session.SessionType; -import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; -import org.thingsboard.server.common.msg.session.ex.SessionException; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.transport.mqtt.MqttTopicMatcher; -import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; - -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * @author Andrew Shvayka - */ -@Slf4j -public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { - - private final MqttTransportAdaptor adaptor; - private final MqttSessionId sessionId; - private ChannelHandlerContext channel; - private volatile boolean allowAttributeResponses; - private AtomicInteger msgIdSeq = new AtomicInteger(0); - - public DeviceSessionCtx(SessionMsgProcessor processor, DeviceAuthService authService, MqttTransportAdaptor adaptor, ConcurrentMap mqttQoSMap) { - super(processor, authService, mqttQoSMap); - this.adaptor = adaptor; - this.sessionId = new MqttSessionId(); - } - - @Override - public SessionType getSessionType() { - return SessionType.ASYNC; - } - - @Override - public void onMsg(SessionActorToAdaptorMsg msg) throws SessionException { - try { - adaptor.convertToAdaptorMsg(this, msg).ifPresent(this::pushToNetwork); - } catch (AdaptorException e) { - //TODO: close channel with disconnect; - logAndWrap(e); - } - } - - private void logAndWrap(AdaptorException e) throws SessionException { - log.warn("Failed to convert msg: {}", e.getMessage(), e); - throw new SessionException(e); - } - - private void pushToNetwork(MqttMessage msg) { - channel.writeAndFlush(msg); - } - - @Override - public void onMsg(SessionCtrlMsg msg) throws SessionException { - if (msg instanceof SessionCloseMsg) { - pushToNetwork(new MqttMessage(new MqttFixedHeader(MqttMessageType.DISCONNECT, false, MqttQoS.AT_MOST_ONCE, false, 0))); - channel.close(); - } - } - - @Override - public boolean isClosed() { - return false; - } - - @Override - public long getTimeout() { - return 0; - } - - @Override - public SessionId getSessionId() { - return sessionId; - } - - public void setChannel(ChannelHandlerContext channel) { - this.channel = channel; - } - - public void setAllowAttributeResponses() { - allowAttributeResponses = true; - } - - public void setDisallowAttributeResponses() { - allowAttributeResponses = false; - } - - public int nextMsgId() { - return msgIdSeq.incrementAndGet(); - } -} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java deleted file mode 100644 index bbc9c086a8..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java +++ /dev/null @@ -1,208 +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.transport.mqtt.session; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.UnpooledByteBufAllocator; -import io.netty.handler.codec.mqtt.*; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.common.msg.session.ex.SessionException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; -import org.thingsboard.server.transport.mqtt.MqttTopicMatcher; -import org.thingsboard.server.transport.mqtt.MqttTopics; -import org.thingsboard.server.transport.mqtt.MqttTransportHandler; - -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Created by ashvayka on 19.01.17. - */ -public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext { - - private static final Gson GSON = new Gson(); - private static final Charset UTF8 = Charset.forName("UTF-8"); - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); - public static final String DEVICE_PROPERTY = "device"; - - private GatewaySessionCtx parent; - private final MqttSessionId sessionId; - private volatile boolean closed; - private AtomicInteger msgIdSeq = new AtomicInteger(0); - - public GatewayDeviceSessionCtx(GatewaySessionCtx parent, Device device, ConcurrentMap mqttQoSMap) { - super(parent.getProcessor(), parent.getAuthService(), device, mqttQoSMap); - this.parent = parent; - this.sessionId = new MqttSessionId(); - } - - @Override - public SessionId getSessionId() { - return sessionId; - } - - @Override - public SessionType getSessionType() { - return SessionType.ASYNC; - } - - @Override - public void onMsg(SessionActorToAdaptorMsg sessionMsg) throws SessionException { - Optional message = getToDeviceMsg(sessionMsg); - message.ifPresent(parent::writeAndFlush); - } - - private Optional getToDeviceMsg(SessionActorToAdaptorMsg sessionMsg) { - ToDeviceMsg msg = sessionMsg.getMsg(); - switch (msg.getSessionMsgType()) { - case STATUS_CODE_RESPONSE: - ResponseMsg responseMsg = (ResponseMsg) msg; - if (responseMsg.isSuccess()) { - SessionMsgType requestMsgType = responseMsg.getRequestMsgType(); - Integer requestId = responseMsg.getRequestId(); - if (requestId >= 0 && (requestMsgType == SessionMsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == SessionMsgType.POST_TELEMETRY_REQUEST)) { - return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId)); - } - } - break; - case GET_ATTRIBUTES_RESPONSE: - GetAttributesResponse response = (GetAttributesResponse) msg; - if (response.isSuccess()) { - return Optional.of(createMqttPublishMsg(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, response)); - } else { - //TODO: push error handling to the gateway - } - break; - case ATTRIBUTES_UPDATE_NOTIFICATION: - AttributesUpdateNotification notification = (AttributesUpdateNotification) msg; - return Optional.of(createMqttPublishMsg(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, notification.getData())); - case TO_DEVICE_RPC_REQUEST: - ToDeviceRpcRequestMsg rpcRequest = (ToDeviceRpcRequestMsg) msg; - return Optional.of(createMqttPublishMsg(MqttTopics.GATEWAY_RPC_TOPIC, rpcRequest)); - default: - break; - } - return Optional.empty(); - } - - @Override - public void onMsg(SessionCtrlMsg msg) throws SessionException { - //Do nothing - } - - @Override - public boolean isClosed() { - return closed; - } - - public void setClosed(boolean closed) { - this.closed = closed; - } - - @Override - public long getTimeout() { - return 0; - } - - private MqttMessage createMqttPublishMsg(String topic, GetAttributesResponse response) { - JsonObject result = new JsonObject(); - result.addProperty("id", response.getRequestId()); - result.addProperty(DEVICE_PROPERTY, device.getName()); - Optional responseData = response.getData(); - if (responseData.isPresent()) { - AttributesKVMsg msg = responseData.get(); - if (msg.getClientAttributes() != null) { - addValues(result, msg.getClientAttributes()); - } - if (msg.getSharedAttributes() != null) { - addValues(result, msg.getSharedAttributes()); - } - } - return createMqttPublishMsg(topic, result); - } - - private void addValues(JsonObject result, List kvList) { - if (kvList.size() == 1) { - addValueToJson(result, "value", kvList.get(0)); - } else { - JsonObject values; - if (result.has("values")) { - values = result.get("values").getAsJsonObject(); - } else { - values = new JsonObject(); - result.add("values", values); - } - kvList.forEach(value -> addValueToJson(values, value.getKey(), value)); - } - } - - private void addValueToJson(JsonObject json, String name, KvEntry entry) { - switch (entry.getDataType()) { - case BOOLEAN: - entry.getBooleanValue().ifPresent(aBoolean -> json.addProperty(name, aBoolean)); - break; - case STRING: - entry.getStrValue().ifPresent(aString -> json.addProperty(name, aString)); - break; - case DOUBLE: - entry.getDoubleValue().ifPresent(aDouble -> json.addProperty(name, aDouble)); - break; - case LONG: - entry.getLongValue().ifPresent(aLong -> json.addProperty(name, aLong)); - break; - } - } - - private MqttMessage createMqttPublishMsg(String topic, AttributesKVMsg data) { - JsonObject result = new JsonObject(); - result.addProperty(DEVICE_PROPERTY, device.getName()); - result.add("data", JsonConverter.toJson(data, false)); - return createMqttPublishMsg(topic, result); - } - - private MqttMessage createMqttPublishMsg(String topic, ToDeviceRpcRequestMsg data) { - JsonObject result = new JsonObject(); - result.addProperty(DEVICE_PROPERTY, device.getName()); - result.add("data", JsonConverter.toJson(data, true)); - return createMqttPublishMsg(topic, result); - } - - private MqttPublishMessage createMqttPublishMsg(String topic, JsonElement json) { - MqttFixedHeader mqttFixedHeader = - new MqttFixedHeader(MqttMessageType.PUBLISH, false, getQoSForTopic(topic), false, 0); - MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, msgIdSeq.incrementAndGet()); - ByteBuf payload = ALLOCATOR.buffer(); - payload.writeBytes(GSON.toJson(json).getBytes(UTF8)); - return new MqttPublishMessage(mqttFixedHeader, header, payload); - } - -} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java deleted file mode 100644 index b9291b794a..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java +++ /dev/null @@ -1,277 +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.transport.mqtt.session; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonNull; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.mqtt.MqttMessage; -import io.netty.handler.codec.mqtt.MqttPublishMessage; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.SessionId; -import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.session.BasicAdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.BasicTransportToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; -import org.thingsboard.server.common.transport.SessionMsgProcessor; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.transport.mqtt.MqttTopicMatcher; -import org.thingsboard.server.transport.mqtt.MqttTransportHandler; -import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; - -import java.util.*; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Collectors; - -import static org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor.validateJsonPayload; - -/** - * Created by ashvayka on 19.01.17. - */ -@Slf4j -public class GatewaySessionCtx { - - private static final String DEFAULT_DEVICE_TYPE = "default"; - public static final String CAN_T_PARSE_VALUE = "Can't parse value: "; - public static final String DEVICE_PROPERTY = "device"; - private final Device gateway; - private final SessionId gatewaySessionId; - private final SessionMsgProcessor processor; - private final DeviceService deviceService; - private final DeviceAuthService authService; - private final RelationService relationService; - private final Map devices; - private final ConcurrentMap mqttQoSMap; - private ChannelHandlerContext channel; - - public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, DeviceSessionCtx gatewaySessionCtx) { - this.processor = processor; - this.deviceService = deviceService; - this.authService = authService; - this.relationService = relationService; - this.gateway = gatewaySessionCtx.getDevice(); - this.gatewaySessionId = gatewaySessionCtx.getSessionId(); - this.devices = new HashMap<>(); - this.mqttQoSMap = gatewaySessionCtx.getMqttQoSMap(); - } - - public void onDeviceConnect(MqttPublishMessage msg) throws AdaptorException { - JsonElement json = getJson(msg); - String deviceName = checkDeviceName(getDeviceName(json)); - String deviceType = getDeviceType(json); - onDeviceConnect(deviceName, deviceType); - ack(msg); - } - - private void onDeviceConnect(String deviceName, String deviceType) { - if (!devices.containsKey(deviceName)) { - Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), deviceName); - if (device == null) { - device = new Device(); - device.setTenantId(gateway.getTenantId()); - device.setName(deviceName); - device.setType(deviceType); - device.setCustomerId(gateway.getCustomerId()); - device = deviceService.saveDevice(device); - relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created")); - processor.onDeviceAdded(device); - } - GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device, mqttQoSMap); - devices.put(deviceName, ctx); - log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName); - processor.process(new BasicTransportToDeviceSessionActorMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg()))); - processor.process(new BasicTransportToDeviceSessionActorMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new RpcSubscribeMsg()))); - } - } - - public void onDeviceDisconnect(MqttPublishMessage msg) throws AdaptorException { - String deviceName = checkDeviceName(getDeviceName(getJson(msg))); - GatewayDeviceSessionCtx deviceSessionCtx = devices.remove(deviceName); - if (deviceSessionCtx != null) { - processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId())); - deviceSessionCtx.setClosed(true); - log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName); - } else { - log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName); - } - ack(msg); - } - - public void onGatewayDisconnect() { - devices.forEach((k, v) -> { - processor.process(SessionCloseMsg.onDisconnect(v.getSessionId())); - }); - } - - public void onDeviceTelemetry(MqttPublishMessage mqttMsg) throws AdaptorException { - JsonElement json = validateJsonPayload(gatewaySessionId, mqttMsg.payload()); - int requestId = mqttMsg.variableHeader().messageId(); - if (json.isJsonObject()) { - JsonObject jsonObj = json.getAsJsonObject(); - for (Map.Entry deviceEntry : jsonObj.entrySet()) { - String deviceName = checkDeviceConnected(deviceEntry.getKey()); - if (!deviceEntry.getValue().isJsonArray()) { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); - } - BasicTelemetryUploadRequest request = new BasicTelemetryUploadRequest(requestId); - JsonArray deviceData = deviceEntry.getValue().getAsJsonArray(); - for (JsonElement element : deviceData) { - JsonConverter.parseWithTs(request, element.getAsJsonObject()); - } - GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), - new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); - } - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); - } - } - - public void onDeviceRpcResponse(MqttPublishMessage mqttMsg) throws AdaptorException { - JsonElement json = validateJsonPayload(gatewaySessionId, mqttMsg.payload()); - if (json.isJsonObject()) { - JsonObject jsonObj = json.getAsJsonObject(); - String deviceName = checkDeviceConnected(jsonObj.get(DEVICE_PROPERTY).getAsString()); - Integer requestId = jsonObj.get("id").getAsInt(); - String data = jsonObj.get("data").toString(); - GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), - new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data)))); - ack(mqttMsg); - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); - } - } - - public void onDeviceAttributes(MqttPublishMessage mqttMsg) throws AdaptorException { - JsonElement json = validateJsonPayload(gatewaySessionId, mqttMsg.payload()); - int requestId = mqttMsg.variableHeader().messageId(); - if (json.isJsonObject()) { - JsonObject jsonObj = json.getAsJsonObject(); - for (Map.Entry deviceEntry : jsonObj.entrySet()) { - String deviceName = checkDeviceConnected(deviceEntry.getKey()); - if (!deviceEntry.getValue().isJsonObject()) { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); - } - long ts = System.currentTimeMillis(); - BasicAttributesUpdateRequest request = new BasicAttributesUpdateRequest(requestId); - JsonObject deviceData = deviceEntry.getValue().getAsJsonObject(); - request.add(JsonConverter.parseValues(deviceData).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); - GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), - new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); - } - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); - } - } - - public void onDeviceAttributesRequest(MqttPublishMessage msg) throws AdaptorException { - JsonElement json = validateJsonPayload(gatewaySessionId, msg.payload()); - if (json.isJsonObject()) { - JsonObject jsonObj = json.getAsJsonObject(); - int requestId = jsonObj.get("id").getAsInt(); - String deviceName = jsonObj.get(DEVICE_PROPERTY).getAsString(); - boolean clientScope = jsonObj.get("client").getAsBoolean(); - Set keys; - if (jsonObj.has("key")) { - keys = Collections.singleton(jsonObj.get("key").getAsString()); - } else { - JsonArray keysArray = jsonObj.get("keys").getAsJsonArray(); - keys = new HashSet<>(); - for (JsonElement keyObj : keysArray) { - keys.add(keyObj.getAsString()); - } - } - - BasicGetAttributesRequest request; - if (clientScope) { - request = new BasicGetAttributesRequest(requestId, keys, null); - } else { - request = new BasicGetAttributesRequest(requestId, null, keys); - } - GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); - processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), - new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); - ack(msg); - } else { - throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); - } - } - - private String checkDeviceConnected(String deviceName) { - if (!devices.containsKey(deviceName)) { - log.debug("[{}] Missing device [{}] for the gateway session", gatewaySessionId, deviceName); - onDeviceConnect(deviceName, DEFAULT_DEVICE_TYPE); - } - return deviceName; - } - - private String checkDeviceName(String deviceName) { - if (StringUtils.isEmpty(deviceName)) { - throw new RuntimeException("Device name is empty!"); - } else { - return deviceName; - } - } - - private String getDeviceName(JsonElement json) throws AdaptorException { - return json.getAsJsonObject().get(DEVICE_PROPERTY).getAsString(); - } - - private String getDeviceType(JsonElement json) throws AdaptorException { - JsonElement type = json.getAsJsonObject().get("type"); - return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString(); - } - - private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException { - return JsonMqttAdaptor.validateJsonPayload(gatewaySessionId, mqttMsg.payload()); - } - - protected SessionMsgProcessor getProcessor() { - return processor; - } - - DeviceAuthService getAuthService() { - return authService; - } - - public void setChannel(ChannelHandlerContext channel) { - this.channel = channel; - } - - private void ack(MqttPublishMessage msg) { - if (msg.variableHeader().messageId() > 0) { - writeAndFlush(MqttTransportHandler.createMqttPubAckMsg(msg.variableHeader().messageId())); - } - } - - void writeAndFlush(MqttMessage mqttMessage) { - channel.writeAndFlush(mqttMessage); - } - -} diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttSessionId.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttSessionId.java deleted file mode 100644 index 03006128bf..0000000000 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttSessionId.java +++ /dev/null @@ -1,63 +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.transport.mqtt.session; - -import org.thingsboard.server.common.data.id.SessionId; - -import java.util.UUID; -import java.util.concurrent.atomic.AtomicLong; - -/** - * @author Andrew Shvayka - */ -public class MqttSessionId implements SessionId { - - private static final AtomicLong idSeq = new AtomicLong(); - - private final long id; - - public MqttSessionId() { - this.id = idSeq.incrementAndGet(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - MqttSessionId that = (MqttSessionId) o; - - return id == that.id; - - } - - @Override - public String toString() { - return "MqttSessionId{" + - "id=" + id + - '}'; - } - - @Override - public int hashCode() { - return (int) (id ^ (id >>> 32)); - } - - @Override - public String toUidStr() { - return "mqtt" + id; - } -} diff --git a/transport/mqtt/src/main/resources/logback.xml b/transport/mqtt/src/main/resources/logback.xml new file mode 100644 index 0000000000..18864a9fab --- /dev/null +++ b/transport/mqtt/src/main/resources/logback.xml @@ -0,0 +1,34 @@ + + + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml new file mode 100644 index 0000000000..a0e86bb1c2 --- /dev/null +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -0,0 +1,95 @@ +# +# 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. +# + +spring.main.web-environment: false +spring.main.web-application-type: none + +# MQTT server parameters +transport: + mqtt: + bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" + bind_port: "${MQTT_BIND_PORT:1883}" + adaptor: "${MQTT_ADAPTOR_NAME:JsonMqttAdaptor}" + timeout: "${MQTT_TIMEOUT:10000}" + netty: + leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" + boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" + worker_group_thread_count: "${NETTY_WORKER_GROUP_THREADS:12}" + max_payload_size: "${NETTY_MAX_PAYLOAD_SIZE:65536}" + # MQTT SSL configuration + ssl: + # Enable/disable SSL support + enabled: "${MQTT_SSL_ENABLED:false}" + # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext + protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" + # Path to the key store that holds the SSL certificate + key_store: "${MQTT_SSL_KEY_STORE:mqttserver.jks}" + # Password used to access the key store + key_store_password: "${MQTT_SSL_KEY_STORE_PASSWORD:server_ks_password}" + # Password used to access the key + key_password: "${MQTT_SSL_KEY_PASSWORD:server_key_password}" + # Type of the key store + key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}" + sessions: + max_per_tenant: "${TB_TRANSPORT_SESSIONS_MAX_PER_TENANT:1000}" + max_per_device: "${TB_TRANSPORT_SESSIONS_MAX_PER_DEVICE:2}" + rate_limits: + enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" + tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" + device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" + +#Quota parameters +quota: + host: + # Max allowed number of API requests in interval for single host + limit: "${QUOTA_HOST_LIMIT:10000}" + # Interval duration + intervalMs: "${QUOTA_HOST_INTERVAL_MS:60000}" + # Maximum silence duration for host after which Host removed from QuotaService. Must be bigger than intervalMs + ttlMs: "${QUOTA_HOST_TTL_MS:60000}" + # Interval for scheduled task that cleans expired records. TTL is used for expiring + cleanPeriodMs: "${QUOTA_HOST_CLEAN_PERIOD_MS:300000}" + # Enable Host API Limits + enabled: "${QUOTA_HOST_ENABLED:true}" + # Array of whitelist hosts + whitelist: "${QUOTA_HOST_WHITELIST:localhost,127.0.0.1}" + # Array of blacklist hosts + blacklist: "${QUOTA_HOST_BLACKLIST:}" + log: + topSize: 10 + intervalMin: 2 + +kafka: + enabled: true + bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" + acks: "${TB_KAFKA_ACKS:all}" + retries: "${TB_KAFKA_RETRIES:1}" + batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" + linger.ms: "${TB_KAFKA_LINGER_MS:1}" + buffer.memory: "${TB_BUFFER_MEMORY:33554432}" + transport_api: + requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" + responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" + max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" + max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + response_poll_interval: "${TB_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" + response_auto_commit_interval: "${TB_TRANSPORT_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}" + rule_engine: + topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}" + notifications: + topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" + poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" + auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" diff --git a/transport/mqtt/src/main/scripts/control/deb/postinst b/transport/mqtt/src/main/scripts/control/deb/postinst new file mode 100644 index 0000000000..d4066c027b --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/deb/postinst @@ -0,0 +1,6 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} +update-rc.d ${pkg.name} defaults + diff --git a/transport/mqtt/src/main/scripts/control/deb/postrm b/transport/mqtt/src/main/scripts/control/deb/postrm new file mode 100644 index 0000000000..61865803c3 --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/deb/postrm @@ -0,0 +1,3 @@ +#!/bin/sh + +update-rc.d -f ${pkg.name} remove diff --git a/transport/mqtt/src/main/scripts/control/deb/preinst b/transport/mqtt/src/main/scripts/control/deb/preinst new file mode 100644 index 0000000000..6be5959285 --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/deb/preinst @@ -0,0 +1,18 @@ +#!/bin/sh + +if ! getent group ${pkg.name} >/dev/null; then + addgroup --system ${pkg.name} +fi + +if ! getent passwd ${pkg.name} >/dev/null; then + adduser --quiet \ + --system \ + --ingroup ${pkg.name} \ + --quiet \ + --disabled-login \ + --disabled-password \ + --home ${pkg.installFolder} \ + --no-create-home \ + -gecos "Thingsboard application" \ + ${pkg.name} +fi diff --git a/transport/mqtt/src/main/scripts/control/deb/prerm b/transport/mqtt/src/main/scripts/control/deb/prerm new file mode 100644 index 0000000000..898d3efd5c --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/deb/prerm @@ -0,0 +1,5 @@ +#!/bin/sh + +if [ -e /var/run/${pkg.name}/${pkg.name}.pid ]; then + service ${pkg.name} stop +fi diff --git a/transport/mqtt/src/main/scripts/control/rpm/postinst b/transport/mqtt/src/main/scripts/control/rpm/postinst new file mode 100644 index 0000000000..8a7a88f7e0 --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/rpm/postinst @@ -0,0 +1,9 @@ +#!/bin/sh + +chown -R ${pkg.name}: ${pkg.logFolder} +chown -R ${pkg.name}: ${pkg.installFolder} + +if [ $1 -eq 1 ] ; then + # Initial installation + systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/mqtt/src/main/scripts/control/rpm/postrm b/transport/mqtt/src/main/scripts/control/rpm/postrm new file mode 100644 index 0000000000..8e1f8a2048 --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/rpm/postrm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -ge 1 ] ; then + # Package upgrade, not uninstall + systemctl try-restart ${pkg.name}.service >/dev/null 2>&1 || : +fi diff --git a/transport/mqtt/src/main/scripts/control/rpm/preinst b/transport/mqtt/src/main/scripts/control/rpm/preinst new file mode 100644 index 0000000000..e19fc884c8 --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/rpm/preinst @@ -0,0 +1,6 @@ +#!/bin/sh + +getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} +getent passwd ${pkg.name} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +-c "Thingsboard application" diff --git a/transport/mqtt/src/main/scripts/control/rpm/prerm b/transport/mqtt/src/main/scripts/control/rpm/prerm new file mode 100644 index 0000000000..accb487b8e --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/rpm/prerm @@ -0,0 +1,6 @@ +#!/bin/sh + +if [ $1 -eq 0 ] ; then + # Package removal, not upgrade + systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : +fi diff --git a/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service b/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service new file mode 100644 index 0000000000..d456fc03c0 --- /dev/null +++ b/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service @@ -0,0 +1,11 @@ +[Unit] +Description=${pkg.name} +After=syslog.target + +[Service] +User=${pkg.name} +ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar +SuccessExitStatus=143 + +[Install] +WantedBy=multi-user.target diff --git a/transport/mqtt/src/main/scripts/windows/install.bat b/transport/mqtt/src/main/scripts/windows/install.bat new file mode 100644 index 0000000000..dba773663c --- /dev/null +++ b/transport/mqtt/src/main/scripts/windows/install.bat @@ -0,0 +1,87 @@ +@ECHO OFF + +setlocal ENABLEEXTENSIONS + +@ECHO Detecting Java version installed. +:CHECK_JAVA_64 +@ECHO Detecting if it is 64 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED + +:CHECK_JAVA_32 +@ECHO Detecting if it is 32 bit machine +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment" +set VALUE_NAME=CurrentVersion + +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName=%%A + set ValueType=%%B + set ValueValue=%%C +) +@ECHO CurrentVersion %ValueValue% + +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%" +SET VALUE_NAME=JavaHome + +if defined ValueName ( + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO ( + set ValueName2=%%A + set ValueType2=%%B + set JRE_PATH2=%%C + + if defined ValueName2 ( + set ValueName = %ValueName2% + set ValueType = %ValueType2% + set ValueValue = %JRE_PATH2% + ) + ) +) + +IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED + +:JAVA_INSTALLED + +@ECHO Java 1.8 found! +@ECHO Installing ${pkg.name} ... + +%BASE%${pkg.name}.exe install + +@ECHO ${pkg.name} installed successfully! + +GOTO END + +:JAVA_NOT_INSTALLED +@ECHO Java 1.8 or above is not installed +@ECHO Please go to https://java.com/ and install Java. Then retry installation. +PAUSE +GOTO END + +:END + + diff --git a/transport/mqtt/src/main/scripts/windows/service.xml b/transport/mqtt/src/main/scripts/windows/service.xml new file mode 100644 index 0000000000..f7b9d307c8 --- /dev/null +++ b/transport/mqtt/src/main/scripts/windows/service.xml @@ -0,0 +1,36 @@ + + ${pkg.name} + ${project.name} + ${project.description} + %BASE%\conf + ${pkg.winWrapperLogFolder} + rotate + + java + -Xloggc:%BASE%\logs\gc.log + -XX:+HeapDumpOnOutOfMemoryError + -XX:+PrintGCDetails + -XX:+PrintGCDateStamps + -XX:+PrintHeapAtGC + -XX:+PrintTenuringDistribution + -XX:+PrintGCApplicationStoppedTime + -XX:+UseGCLogFileRotation + -XX:NumberOfGCLogFiles=10 + -XX:GCLogFileSize=10M + -XX:-UseBiasedLocking + -XX:+UseTLAB + -XX:+ResizeTLAB + -XX:+PerfDisableSharedMem + -XX:+UseCondCardMark + -XX:CMSWaitDuration=10000 + -XX:+UseParNewGC + -XX:+UseConcMarkSweepGC + -XX:+CMSParallelRemarkEnabled + -XX:+CMSParallelInitialMarkEnabled + -XX:+CMSEdenChunksRecordAlways + -XX:CMSInitiatingOccupancyFraction=75 + -XX:+UseCMSInitiatingOccupancyOnly + -jar + %BASE%\lib\${pkg.name}.jar + + diff --git a/transport/mqtt/src/main/scripts/windows/uninstall.bat b/transport/mqtt/src/main/scripts/windows/uninstall.bat new file mode 100644 index 0000000000..921e4c8395 --- /dev/null +++ b/transport/mqtt/src/main/scripts/windows/uninstall.bat @@ -0,0 +1,9 @@ +@ECHO OFF + +@ECHO Stopping ${pkg.name} ... +net stop ${pkg.name} + +@ECHO Uninstalling ${pkg.name} ... +%~dp0${pkg.name}.exe uninstall + +@ECHO DONE. \ No newline at end of file diff --git a/transport/pom.xml b/transport/pom.xml index 95b0112a2a..0bb9300b27 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -23,7 +23,6 @@ 2.2.0-SNAPSHOT thingsboard - org.thingsboard transport pom @@ -36,23 +35,8 @@ http - coap mqtt + coap - - - org.thingsboard - dao - - - org.springframework.boot - spring-boot-autoconfigure - - - org.bouncycastle - bcprov-jdk15on - - - diff --git a/ui/src/app/api/entity-view.service.js b/ui/src/app/api/entity-view.service.js index e34d3a4aee..23fefbc05b 100644 --- a/ui/src/app/api/entity-view.service.js +++ b/ui/src/app/api/entity-view.service.js @@ -27,7 +27,6 @@ function EntityViewService($http, $q, $window, userService, attributeService, cu deleteEntityView: deleteEntityView, getCustomerEntityViews: getCustomerEntityViews, getEntityView: getEntityView, - getEntityViews: getEntityViews, getTenantEntityViews: getTenantEntityViews, saveEntityView: saveEntityView, unassignEntityViewFromCustomer: unassignEntityViewFromCustomer, @@ -126,32 +125,6 @@ function EntityViewService($http, $q, $window, userService, attributeService, cu return deferred.promise; } - function getEntityViews(entityViewIds, config) { - var deferred = $q.defer(); - var ids = ''; - for (var i=0;i0) { - ids += ','; - } - ids += entityViewIds[i]; - } - var url = '/api/entityViews?entityViewIds=' + ids; - $http.get(url, config).then(function success(response) { - var entityViews = response.data; - entityViews.sort(function (entityView1, entityView2) { - var id1 = entityView1.id.id; - var id2 = entityView2.id.id; - var index1 = entityViewIds.indexOf(id1); - var index2 = entityViewIds.indexOf(id2); - return index1 - index2; - }); - deferred.resolve(entityViews); - }, function fail(response) { - deferred.reject(response.data); - }); - return deferred.promise; - } - function saveEntityView(entityView) { var deferred = $q.defer(); var url = '/api/entityView'; diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 205abc9e4e..00c7ecf990 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -135,6 +135,10 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device case types.entityType.asset: promise = assetService.getAssets(entityIds, config); break; + case types.entityType.entityView: + promise = getEntitiesByIdsPromise( + (id) => entityViewService.getEntityView(id, config), entityIds); + break; case types.entityType.tenant: promise = getEntitiesByIdsPromise( (id) => tenantService.getTenant(id, config), entityIds); diff --git a/ui/src/app/entity-view/add-entity-view.tpl.html b/ui/src/app/entity-view/add-entity-view.tpl.html index 48a1788ed0..ebf1f1d341 100644 --- a/ui/src/app/entity-view/add-entity-view.tpl.html +++ b/ui/src/app/entity-view/add-entity-view.tpl.html @@ -15,7 +15,7 @@ limitations under the License. --> - +
diff --git a/ui/src/app/entity-view/entity-view-fieldset.tpl.html b/ui/src/app/entity-view/entity-view-fieldset.tpl.html index 66dc116cc8..766f8c82a1 100644 --- a/ui/src/app/entity-view/entity-view-fieldset.tpl.html +++ b/ui/src/app/entity-view/entity-view-fieldset.tpl.html @@ -60,7 +60,7 @@ entity-type="types.entityType.entityView">
- +
+ + + +
{{ 'entity-view.attributes-propagation' | translate }}
+ + +
+ + +
{{ 'entity-view.attributes-propagation' | translate }}
+ + +
+ +
entity-view.attributes-propagation-hint
+ + + + {{item}} + + + + + + {{item}} + + + + + + {{item}} + + +
+
+
+ + +
{{ 'entity-view.timeseries-data' | translate }}
+ + +
+ + +
{{ 'entity-view.timeseries-data' | translate }}
+ + +
+ +
entity-view.timeseries-data-hint
+ + + + {{item}} + + +
+
+
+
+
+ + +
+
+ + +
-
- - - - - - - - - - - - -
-
-
- - -
-
- - -
-
diff --git a/ui/src/app/entity-view/entity-view.directive.js b/ui/src/app/entity-view/entity-view.directive.js index e1ae82fa6a..25377f4cb4 100644 --- a/ui/src/app/entity-view/entity-view.directive.js +++ b/ui/src/app/entity-view/entity-view.directive.js @@ -13,6 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import './entity-view.scss'; + /* eslint-disable import/no-unresolved, import/default */ import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html'; @@ -20,12 +23,16 @@ import entityViewFieldsetTemplate from './entity-view-fieldset.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ /*@ngInject*/ -export default function EntityViewDirective($compile, $templateCache, $filter, toast, $translate, $mdConstant, - types, clipboardService, entityViewService, customerService) { +export default function EntityViewDirective($q, $compile, $templateCache, $filter, toast, $translate, $mdConstant, $mdExpansionPanel, + types, clipboardService, entityViewService, customerService, entityService) { var linker = function (scope, element) { var template = $templateCache.get(entityViewFieldsetTemplate); element.html(template); + scope.attributesPanelId = (Math.random()*1000).toFixed(0); + scope.timeseriesPanelId = (Math.random()*1000).toFixed(0); + scope.$mdExpansionPanel = $mdExpansionPanel; + scope.types = types; scope.isAssignedToCustomer = false; scope.isPublic = false; @@ -53,9 +60,13 @@ export default function EntityViewDirective($compile, $templateCache, $filter, t } if (scope.entityView.startTimeMs > 0) { scope.startTimeMs = new Date(scope.entityView.startTimeMs); + } else { + scope.startTimeMs = null; } if (scope.entityView.endTimeMs > 0) { scope.endTimeMs = new Date(scope.entityView.endTimeMs); + } else { + scope.endTimeMs = null; } if (!scope.entityView.keys) { scope.entityView.keys = {}; @@ -68,6 +79,19 @@ export default function EntityViewDirective($compile, $templateCache, $filter, t } }); + scope.dataKeysSearch = function (searchText, type) { + var deferred = $q.defer(); + entityService.getEntityKeys(scope.entityView.entityId.entityType, scope.entityView.entityId.id, searchText, type, {ignoreLoading: true}).then( + function success(keys) { + deferred.resolve(keys); + }, + function fail() { + deferred.resolve([]); + } + ); + return deferred.promise; + + }; scope.$watch('startTimeMs', function (newDate) { if (newDate) { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java b/ui/src/app/entity-view/entity-view.scss similarity index 55% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java rename to ui/src/app/entity-view/entity-view.scss index dcfde0fb52..7b16b4a85d 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java +++ b/ui/src/app/entity-view/entity-view.scss @@ -13,31 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.msg.core; -/** - * @author Andrew Shvayka - */ - -public enum RuleEngineError { - - QUEUE_PUT_TIMEOUT(true), SERVER_ERROR(true), TIMEOUT; +@import "../../scss/constants"; - private final boolean critical; +.tb-entity-view-panel-group { + .tb-panel-title { + min-width: 90px; + user-select: none; - RuleEngineError() { - this(false); + @media (min-width: $layout-breakpoint-sm) { + min-width: 180px; } - - RuleEngineError(boolean critical) { - this.critical = critical; - } - - public boolean isCritical() { - return critical; + } + + .tb-panel-prompt { + overflow: hidden; + font-size: 14px; + color: rgba(0, 0, 0, .87); + text-overflow: ellipsis; + white-space: nowrap; + } + + &.disabled { + .tb-panel-title, + .tb-panel-prompt { + color: rgba(0, 0, 0, .38); } + } - public int getPriority() { - return ordinal(); - } + md-icon.md-expansion-panel-icon { + margin-right: 0; + } } diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 55a17223ac..1fde4b6d47 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -838,14 +838,24 @@ "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", "select-entity-view": "Select entity view", "make-public": "Make entity view public", + "start-date": "Start date", "start-ts": "Start time", + "end-date": "End date", "end-ts": "End time", "date-limits": "Date limits", "client-attributes": "Client attributes", "shared-attributes": "Shared attributes", "server-attributes": "Server attributes", - "latest-timeseries": "Latest timeseries", - "related-entity": "Related entity" + "timeseries": "Timeseries", + "client-attributes-placeholder": "Client attributes", + "shared-attributes-placeholder": "Shared attributes", + "server-attributes-placeholder": "Server attributes", + "timeseries-placeholder": "Timeseries", + "target-entity": "Target entity", + "attributes-propagation": "Attributes propagation", + "attributes-propagation-hint": "Entity View will automatically copy specified attributes from Target Entity each time you save or update this entity view. For performance reasons target entity attributes are not propagated to entity view on each attribute change. You can enable automatic propagation by configuring \"copy to view\" rule node in your rule chain and linking \"Post attributes\" and \"Attributes Updated\" messages to the new rule node.", + "timeseries-data": "Timeseries data", + "timeseries-data-hint": "Configure timeseries data keys of the target entity that will be accessible to the entity view. This timeseries data is read-only." }, "event": { "event-type": "Event type", diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index ec0e2272d2..81cc52ed6f 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -839,7 +839,7 @@ "client-attributes": "Client attributes", "shared-attributes": "Shared attributes", "server-attributes": "Server attributes", - "latest-timeseries": "Latest timeseries" + "timeseries": "Timeseries" }, "event": { "event-type": "Tipo de evento",