Commit fa27fd42c70a0e8de244a9cc391391c3acdeb16f

Authored by Andrew Shvayka
2 parents a193bf16 9629cd0c

Merge branch 'master' of github.com:thingsboard/thingsboard

... ... @@ -34,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value;
34 34 import org.springframework.context.annotation.Lazy;
35 35 import org.springframework.stereotype.Component;
36 36 import org.thingsboard.rule.engine.api.MailService;
  37 +import org.thingsboard.rule.engine.api.RuleChainTransactionService;
37 38 import org.thingsboard.server.actors.service.ActorService;
38 39 import org.thingsboard.server.common.data.DataConstants;
39 40 import org.thingsboard.server.common.data.Event;
... ... @@ -222,6 +223,11 @@ public class ActorSystemContext {
222 223 @Getter
223 224 private RuleEngineTransportService ruleEngineTransportService;
224 225
  226 + @Lazy
  227 + @Autowired
  228 + @Getter
  229 + private RuleChainTransactionService ruleChainTransactionService;
  230 +
225 231 @Value("${cluster.partition_id}")
226 232 @Getter
227 233 private long queuePartitionId;
... ...
... ... @@ -20,6 +20,7 @@ import com.datastax.driver.core.utils.UUIDs;
20 20 import org.springframework.util.StringUtils;
21 21 import org.thingsboard.rule.engine.api.ListeningExecutor;
22 22 import org.thingsboard.rule.engine.api.MailService;
  23 +import org.thingsboard.rule.engine.api.RuleChainTransactionService;
23 24 import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
24 25 import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse;
25 26 import org.thingsboard.rule.engine.api.RuleEngineRpcService;
... ... @@ -124,7 +125,7 @@ class DefaultTbContext implements TbContext {
124 125
125 126 @Override
126 127 public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
127   - return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), mainCtx.getQueuePartitionId());
  128 + return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), origMsg.getDataType(), data, origMsg.getTransactionData(), origMsg.getRuleChainId(), origMsg.getRuleNodeId(), mainCtx.getQueuePartitionId());
128 129 }
129 130
130 131 @Override
... ... @@ -233,6 +234,11 @@ class DefaultTbContext implements TbContext {
233 234 }
234 235
235 236 @Override
  237 + public RuleChainTransactionService getRuleChainTransactionService() {
  238 + return mainCtx.getRuleChainTransactionService();
  239 + }
  240 +
  241 + @Override
236 242 public MailService getMailService() {
237 243 if (mainCtx.isAllowSystemMailService()) {
238 244 return mainCtx.getMailService();
... ...
... ... @@ -56,12 +56,8 @@ import scala.concurrent.duration.Duration;
56 56
57 57 import javax.annotation.PostConstruct;
58 58 import javax.annotation.PreDestroy;
59   -
60   -import java.util.Arrays;
61   -import java.util.UUID;
62 59 import java.util.concurrent.Executors;
63 60 import java.util.concurrent.ScheduledExecutorService;
64   -import java.util.concurrent.TimeUnit;
65 61
66 62 import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE;
67 63
... ... @@ -235,6 +231,9 @@ public class DefaultActorService implements ActorService {
235 231 case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE:
236 232 actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray());
237 233 break;
  234 + case CLUSTER_TRANSACTION_SERVICE_MESSAGE:
  235 + actorContext.getRuleChainTransactionService().onRemoteTransactionMsg(serverAddress, msg.getPayload().toByteArray());
  236 + break;
238 237 }
239 238 }
240 239
... ...
... ... @@ -19,8 +19,6 @@ import org.thingsboard.server.common.data.Device;
19 19 import org.thingsboard.server.common.data.id.DeviceId;
20 20 import org.thingsboard.server.common.msg.cluster.ServerAddress;
21 21
22   -import java.util.Optional;
23   -
24 22 /**
25 23 * Created by ashvayka on 01.05.18.
26 24 */
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.transaction;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.beans.factory.annotation.Value;
  22 +import org.springframework.stereotype.Service;
  23 +import org.thingsboard.rule.engine.api.RuleChainTransactionService;
  24 +import org.thingsboard.server.common.data.id.EntityId;
  25 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  26 +import org.thingsboard.server.common.msg.TbMsg;
  27 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  28 +import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  29 +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
  30 +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
  31 +import org.thingsboard.server.service.executors.DbCallbackExecutorService;
  32 +
  33 +import javax.annotation.PostConstruct;
  34 +import javax.annotation.PreDestroy;
  35 +import java.util.Optional;
  36 +import java.util.Queue;
  37 +import java.util.UUID;
  38 +import java.util.concurrent.BlockingQueue;
  39 +import java.util.concurrent.Callable;
  40 +import java.util.concurrent.ConcurrentHashMap;
  41 +import java.util.concurrent.ConcurrentLinkedQueue;
  42 +import java.util.concurrent.ConcurrentMap;
  43 +import java.util.concurrent.ExecutorService;
  44 +import java.util.concurrent.Executors;
  45 +import java.util.concurrent.LinkedBlockingQueue;
  46 +import java.util.concurrent.TimeUnit;
  47 +import java.util.concurrent.locks.Lock;
  48 +import java.util.concurrent.locks.ReentrantLock;
  49 +import java.util.function.Consumer;
  50 +
  51 +@Service
  52 +@Slf4j
  53 +public class BaseRuleChainTransactionService implements RuleChainTransactionService {
  54 +
  55 + @Autowired
  56 + private ClusterRoutingService routingService;
  57 +
  58 + @Autowired
  59 + private ClusterRpcService clusterRpcService;
  60 +
  61 + @Autowired
  62 + private DbCallbackExecutorService callbackExecutor;
  63 +
  64 + @Value("${actors.rule.transaction.queue_size}")
  65 + private int finalQueueSize;
  66 + @Value("${actors.rule.transaction.duration}")
  67 + private long duration;
  68 +
  69 + private final Lock transactionLock = new ReentrantLock();
  70 + private final ConcurrentMap<EntityId, BlockingQueue<TbTransactionTask>> transactionMap = new ConcurrentHashMap<>();
  71 + private final Queue<TbTransactionTask> timeoutQueue = new ConcurrentLinkedQueue<>();
  72 +
  73 + private ExecutorService timeoutExecutor;
  74 +
  75 + @PostConstruct
  76 + public void init() {
  77 + timeoutExecutor = Executors.newSingleThreadExecutor();
  78 + executeOnTimeout();
  79 + }
  80 +
  81 + @PreDestroy
  82 + public void destroy() {
  83 + if (timeoutExecutor != null) {
  84 + timeoutExecutor.shutdownNow();
  85 + }
  86 + }
  87 +
  88 + @Override
  89 + public void beginTransaction(TbMsg msg, Consumer<TbMsg> onStart, Consumer<TbMsg> onEnd, Consumer<Throwable> onFailure) {
  90 + transactionLock.lock();
  91 + try {
  92 + BlockingQueue<TbTransactionTask> queue = transactionMap.computeIfAbsent(msg.getTransactionData().getOriginatorId(), id ->
  93 + new LinkedBlockingQueue<>(finalQueueSize));
  94 +
  95 + TbTransactionTask transactionTask = new TbTransactionTask(msg, onStart, onEnd, onFailure, System.currentTimeMillis() + duration);
  96 + int queueSize = queue.size();
  97 + if (queueSize >= finalQueueSize) {
  98 + executeOnFailure(transactionTask.getOnFailure(), "Queue has no space!");
  99 + } else {
  100 + addMsgToQueues(queue, transactionTask);
  101 + if (queueSize == 0) {
  102 + executeOnSuccess(transactionTask.getOnStart(), transactionTask.getMsg());
  103 + } else {
  104 + log.trace("Msg [{}][{}] is waiting to start transaction!", msg.getId(), msg.getType());
  105 + }
  106 + }
  107 + } finally {
  108 + transactionLock.unlock();
  109 + }
  110 + }
  111 +
  112 + @Override
  113 + public void endTransaction(TbMsg msg, Consumer<TbMsg> onSuccess, Consumer<Throwable> onFailure) {
  114 + EntityId originatorId = msg.getTransactionData().getOriginatorId();
  115 + UUID transactionId = msg.getTransactionData().getTransactionId();
  116 +
  117 + Optional<ServerAddress> address = routingService.resolveById(originatorId);
  118 + if (address.isPresent()) {
  119 + sendTransactionEventToRemoteServer(originatorId, transactionId, address.get());
  120 + executeOnSuccess(onSuccess, msg);
  121 + } else {
  122 + endLocalTransaction(transactionId, originatorId, onSuccess, onFailure);
  123 + }
  124 + }
  125 +
  126 + @Override
  127 + public void onRemoteTransactionMsg(ServerAddress serverAddress, byte[] data) {
  128 + ClusterAPIProtos.TransactionEndServiceMsgProto proto;
  129 + try {
  130 + proto = ClusterAPIProtos.TransactionEndServiceMsgProto.parseFrom(data);
  131 + } catch (InvalidProtocolBufferException e) {
  132 + throw new RuntimeException(e);
  133 + }
  134 + EntityId originatorId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getOriginatorIdMSB(), proto.getOriginatorIdLSB()));
  135 + UUID transactionId = new UUID(proto.getTransactionIdMSB(), proto.getTransactionIdLSB());
  136 + endLocalTransaction(transactionId, originatorId, msg -> {
  137 + }, error -> {
  138 + });
  139 + }
  140 +
  141 + private void addMsgToQueues(BlockingQueue<TbTransactionTask> queue, TbTransactionTask transactionTask) {
  142 + queue.offer(transactionTask);
  143 + timeoutQueue.offer(transactionTask);
  144 + log.trace("Added msg to queue, size: [{}]", queue.size());
  145 + }
  146 +
  147 + private void endLocalTransaction(UUID transactionId, EntityId originatorId, Consumer<TbMsg> onSuccess, Consumer<Throwable> onFailure) {
  148 + transactionLock.lock();
  149 + try {
  150 + BlockingQueue<TbTransactionTask> queue = transactionMap.computeIfAbsent(originatorId, id ->
  151 + new LinkedBlockingQueue<>(finalQueueSize));
  152 +
  153 + TbTransactionTask currentTransactionTask = queue.peek();
  154 + if (currentTransactionTask != null) {
  155 + if (currentTransactionTask.getMsg().getTransactionData().getTransactionId().equals(transactionId)) {
  156 + currentTransactionTask.setCompleted(true);
  157 + queue.poll();
  158 + log.trace("Removed msg from queue, size [{}]", queue.size());
  159 +
  160 + executeOnSuccess(currentTransactionTask.getOnEnd(), currentTransactionTask.getMsg());
  161 + executeOnSuccess(onSuccess, currentTransactionTask.getMsg());
  162 +
  163 + TbTransactionTask nextTransactionTask = queue.peek();
  164 + if (nextTransactionTask != null) {
  165 + executeOnSuccess(nextTransactionTask.getOnStart(), nextTransactionTask.getMsg());
  166 + }
  167 + } else {
  168 + log.trace("Task has expired!");
  169 + executeOnFailure(onFailure, "Task has expired!");
  170 + }
  171 + } else {
  172 + log.trace("Queue is empty, previous task has expired!");
  173 + executeOnFailure(onFailure, "Queue is empty, previous task has expired!");
  174 + }
  175 + } finally {
  176 + transactionLock.unlock();
  177 + }
  178 + }
  179 +
  180 + private void executeOnTimeout() {
  181 + timeoutExecutor.submit(() -> {
  182 + while (true) {
  183 + TbTransactionTask transactionTask = timeoutQueue.peek();
  184 + if (transactionTask != null) {
  185 + transactionLock.lock();
  186 + try {
  187 + if (transactionTask.isCompleted()) {
  188 + timeoutQueue.poll();
  189 + } else {
  190 + if (System.currentTimeMillis() > transactionTask.getExpirationTime()) {
  191 + log.trace("Task has expired! Deleting it...[{}][{}]", transactionTask.getMsg().getId(), transactionTask.getMsg().getType());
  192 + timeoutQueue.poll();
  193 + executeOnFailure(transactionTask.getOnFailure(), "Task has expired!");
  194 +
  195 + BlockingQueue<TbTransactionTask> queue = transactionMap.get(transactionTask.getMsg().getTransactionData().getOriginatorId());
  196 + if (queue != null) {
  197 + queue.poll();
  198 + TbTransactionTask nextTransactionTask = queue.peek();
  199 + if (nextTransactionTask != null) {
  200 + executeOnSuccess(nextTransactionTask.getOnStart(), nextTransactionTask.getMsg());
  201 + }
  202 + }
  203 + } else {
  204 + try {
  205 + log.trace("Task has not expired! Continue executing...[{}][{}]", transactionTask.getMsg().getId(), transactionTask.getMsg().getType());
  206 + TimeUnit.MILLISECONDS.sleep(duration);
  207 + } catch (InterruptedException e) {
  208 + throw new IllegalStateException("Thread interrupted", e);
  209 + }
  210 + }
  211 + }
  212 + } finally {
  213 + transactionLock.unlock();
  214 + }
  215 + } else {
  216 + try {
  217 + log.trace("Queue is empty, waiting for tasks!");
  218 + TimeUnit.SECONDS.sleep(1);
  219 + } catch (InterruptedException e) {
  220 + throw new IllegalStateException("Thread interrupted", e);
  221 + }
  222 + }
  223 + }
  224 + });
  225 + }
  226 +
  227 + private void executeOnFailure(Consumer<Throwable> onFailure, String exception) {
  228 + executeCallback(() -> {
  229 + onFailure.accept(new RuntimeException(exception));
  230 + return null;
  231 + });
  232 + }
  233 +
  234 + private void executeOnSuccess(Consumer<TbMsg> onSuccess, TbMsg tbMsg) {
  235 + executeCallback(() -> {
  236 + onSuccess.accept(tbMsg);
  237 + return null;
  238 + });
  239 + }
  240 +
  241 + private void executeCallback(Callable<Void> task) {
  242 + callbackExecutor.executeAsync(task);
  243 + }
  244 +
  245 + private void sendTransactionEventToRemoteServer(EntityId entityId, UUID transactionId, ServerAddress address) {
  246 + log.trace("[{}][{}] Originator is monitored on other server: {}", entityId, transactionId, address);
  247 + ClusterAPIProtos.TransactionEndServiceMsgProto.Builder builder = ClusterAPIProtos.TransactionEndServiceMsgProto.newBuilder();
  248 + builder.setEntityType(entityId.getEntityType().name());
  249 + builder.setOriginatorIdMSB(entityId.getId().getMostSignificantBits());
  250 + builder.setOriginatorIdLSB(entityId.getId().getLeastSignificantBits());
  251 + builder.setTransactionIdMSB(transactionId.getMostSignificantBits());
  252 + builder.setTransactionIdLSB(transactionId.getLeastSignificantBits());
  253 + clusterRpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TRANSACTION_SERVICE_MESSAGE, builder.build().toByteArray());
  254 + }
  255 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.transaction;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.msg.TbMsg;
  21 +
  22 +import java.util.function.Consumer;
  23 +
  24 +@Data
  25 +@AllArgsConstructor
  26 +public final class TbTransactionTask {
  27 +
  28 + private final TbMsg msg;
  29 + private final Consumer<TbMsg> onStart;
  30 + private final Consumer<TbMsg> onEnd;
  31 + private final Consumer<Throwable> onFailure;
  32 + private final long expirationTime;
  33 +
  34 + private boolean isCompleted;
  35 +
  36 + public TbTransactionTask(TbMsg msg, Consumer<TbMsg> onStart, Consumer<TbMsg> onEnd, Consumer<Throwable> onFailure, long expirationTime) {
  37 + this.msg = msg;
  38 + this.onStart = onStart;
  39 + this.onEnd = onEnd;
  40 + this.onFailure = onFailure;
  41 + this.expirationTime = expirationTime;
  42 + this.isCompleted = false;
  43 + }
  44 +}
... ...
... ... @@ -60,6 +60,7 @@ enum MessageType {
60 60 CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE = 12;
61 61
62 62 CLUSTER_DEVICE_STATE_SERVICE_MESSAGE = 13;
  63 + CLUSTER_TRANSACTION_SERVICE_MESSAGE = 14;
63 64 }
64 65
65 66 // Messages related to CLUSTER_TELEMETRY_MESSAGE
... ... @@ -142,3 +143,11 @@ message DeviceStateServiceMsgProto {
142 143 bool updated = 6;
143 144 bool deleted = 7;
144 145 }
  146 +
  147 +message TransactionEndServiceMsgProto {
  148 + string entityType = 1;
  149 + int64 originatorIdMSB = 2;
  150 + int64 originatorIdLSB = 3;
  151 + int64 transactionIdMSB = 4;
  152 + int64 transactionIdLSB = 5;
  153 +}
... ...
... ... @@ -213,6 +213,11 @@ actors:
213 213 node:
214 214 # Errors for particular actor are persisted once per specified amount of milliseconds
215 215 error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}"
  216 + transaction:
  217 + # Size of queues which store messages for transaction rule nodes
  218 + queue_size: "${ACTORS_RULE_TRANSACTION_QUEUE_SIZE:20}"
  219 + # Time in milliseconds for transaction to complete
  220 + duration: "${ACTORS_RULE_TRANSACTION_DURATION:15000}"
216 221 statistics:
217 222 # Enable/disable actor statistics
218 223 enabled: "${ACTORS_STATISTICS_ENABLED:true}"
... ... @@ -310,7 +315,7 @@ spring:
310 315 password: "${SPRING_DATASOURCE_PASSWORD:}"
311 316
312 317 # PostgreSQL DAO Configuration
313   -# spring:
  318 +#spring:
314 319 # data:
315 320 # sql:
316 321 # repositories:
... ...
... ... @@ -41,6 +41,7 @@ public final class TbMsg implements Serializable {
41 41 private final TbMsgMetaData metaData;
42 42 private final TbMsgDataType dataType;
43 43 private final String data;
  44 + private final TbMsgTransactionData transactionData;
44 45
45 46 //The following fields are not persisted to DB, because they can always be recovered from the context;
46 47 private final RuleChainId ruleChainId;
... ... @@ -55,11 +56,17 @@ public final class TbMsg implements Serializable {
55 56 this.metaData = metaData;
56 57 this.data = data;
57 58 this.dataType = TbMsgDataType.JSON;
  59 + this.transactionData = new TbMsgTransactionData(id, originator);
58 60 this.ruleChainId = ruleChainId;
59 61 this.ruleNodeId = ruleNodeId;
60 62 this.clusterPartition = clusterPartition;
61 63 }
62 64
  65 + public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
  66 + RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) {
  67 + this(id, type, originator, metaData, dataType, data, new TbMsgTransactionData(id, originator), ruleChainId, ruleNodeId, clusterPartition);
  68 + }
  69 +
63 70 public static ByteBuffer toBytes(TbMsg msg) {
64 71 MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder();
65 72 builder.setId(msg.getId().toString());
... ... @@ -82,6 +89,16 @@ public final class TbMsg implements Serializable {
82 89 builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build());
83 90 }
84 91
  92 + TbMsgTransactionData transactionData = msg.getTransactionData();
  93 + if (transactionData != null) {
  94 + MsgProtos.TbMsgTransactionDataProto.Builder transactionBuilder = MsgProtos.TbMsgTransactionDataProto.newBuilder();
  95 + transactionBuilder.setId(transactionData.getTransactionId().toString());
  96 + transactionBuilder.setEntityType(transactionData.getOriginatorId().getEntityType().name());
  97 + transactionBuilder.setEntityIdMSB(transactionData.getOriginatorId().getId().getMostSignificantBits());
  98 + transactionBuilder.setEntityIdLSB(transactionData.getOriginatorId().getId().getLeastSignificantBits());
  99 + builder.setTransactionData(transactionBuilder.build());
  100 + }
  101 +
85 102 builder.setDataType(msg.getDataType().ordinal());
86 103 builder.setData(msg.getData());
87 104 byte[] bytes = builder.build().toByteArray();
... ... @@ -92,6 +109,9 @@ public final class TbMsg implements Serializable {
92 109 try {
93 110 MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array());
94 111 TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap());
  112 + EntityId transactionEntityId = EntityIdFactory.getByTypeAndUuid(proto.getTransactionData().getEntityType(),
  113 + new UUID(proto.getTransactionData().getEntityIdMSB(), proto.getTransactionData().getEntityIdLSB()));
  114 + TbMsgTransactionData transactionData = new TbMsgTransactionData(UUID.fromString(proto.getTransactionData().getId()), transactionEntityId);
95 115 EntityId entityId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB()));
96 116 RuleChainId ruleChainId = new RuleChainId(new UUID(proto.getRuleChainIdMSB(), proto.getRuleChainIdLSB()));
97 117 RuleNodeId ruleNodeId = null;
... ... @@ -99,7 +119,7 @@ public final class TbMsg implements Serializable {
99 119 ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB()));
100 120 }
101 121 TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
102   - return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, proto.getClusterPartition());
  122 + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), transactionData, ruleChainId, ruleNodeId, proto.getClusterPartition());
103 123 } catch (InvalidProtocolBufferException e) {
104 124 throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
105 125 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.msg;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.EntityId;
  20 +
  21 +import java.io.Serializable;
  22 +import java.util.UUID;
  23 +
  24 +@Data
  25 +public final class TbMsgTransactionData implements Serializable {
  26 +
  27 + private final UUID transactionId;
  28 + private final EntityId originatorId;
  29 +
  30 +}
... ...
... ... @@ -23,6 +23,13 @@ message TbMsgMetaDataProto {
23 23 map<string, string> data = 1;
24 24 }
25 25
  26 +message TbMsgTransactionDataProto {
  27 + string id = 1;
  28 + string entityType = 2;
  29 + int64 entityIdMSB = 3;
  30 + int64 entityIdLSB = 4;
  31 +}
  32 +
26 33 message TbMsgProto {
27 34 string id = 1;
28 35 string type = 2;
... ... @@ -39,7 +46,9 @@ message TbMsgProto {
39 46
40 47 TbMsgMetaDataProto metaData = 11;
41 48
42   - int32 dataType = 12;
43   - string data = 13;
  49 + TbMsgTransactionDataProto transactionData = 12;
  50 +
  51 + int32 dataType = 13;
  52 + string data = 14;
44 53
45 54 }
\ No newline at end of file
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.api;
  17 +
  18 +import org.thingsboard.server.common.msg.TbMsg;
  19 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  20 +
  21 +import java.util.function.Consumer;
  22 +
  23 +public interface RuleChainTransactionService {
  24 +
  25 + void beginTransaction(TbMsg msg, Consumer<TbMsg> onStart, Consumer<TbMsg> onEnd, Consumer<Throwable> onFailure);
  26 +
  27 + void endTransaction(TbMsg msg, Consumer<TbMsg> onSuccess, Consumer<Throwable> onFailure);
  28 +
  29 + void onRemoteTransactionMsg(ServerAddress serverAddress, byte[] bytes);
  30 +
  31 +}
... ...
... ... @@ -103,4 +103,6 @@ public interface TbContext {
103 103 ScriptEngine createJsScriptEngine(String script, String... argNames);
104 104
105 105 String getNodeId();
  106 +
  107 + RuleChainTransactionService getRuleChainTransactionService();
106 108 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.transaction;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
  20 +import org.thingsboard.rule.engine.api.RuleNode;
  21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.api.TbNode;
  23 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  24 +import org.thingsboard.rule.engine.api.TbNodeException;
  25 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  26 +import org.thingsboard.server.common.data.plugin.ComponentType;
  27 +import org.thingsboard.server.common.msg.TbMsg;
  28 +import org.thingsboard.server.common.msg.TbMsgDataType;
  29 +import org.thingsboard.server.common.msg.TbMsgTransactionData;
  30 +
  31 +import java.util.concurrent.ExecutionException;
  32 +
  33 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
  34 +
  35 +@Slf4j
  36 +@RuleNode(
  37 + type = ComponentType.ACTION,
  38 + name = "transaction start",
  39 + configClazz = EmptyNodeConfiguration.class,
  40 + nodeDescription = "",
  41 + nodeDetails = "",
  42 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  43 + configDirective = "tbNodeEmptyConfig")
  44 +public class TbTransactionBeginNode implements TbNode {
  45 +
  46 + private EmptyNodeConfiguration config;
  47 +
  48 + @Override
  49 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  50 + this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class);
  51 + }
  52 +
  53 + @Override
  54 + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
  55 + log.trace("Msg enters transaction - [{}][{}]", msg.getId(), msg.getType());
  56 +
  57 + TbMsgTransactionData transactionData = new TbMsgTransactionData(msg.getId(), msg.getOriginator());
  58 + TbMsg tbMsg = new TbMsg(msg.getId(), msg.getType(), msg.getOriginator(), msg.getMetaData(), TbMsgDataType.JSON,
  59 + msg.getData(), transactionData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition());
  60 +
  61 + ctx.getRuleChainTransactionService().beginTransaction(tbMsg, startMsg -> {
  62 + log.trace("Transaction starting...[{}][{}]", startMsg.getId(), startMsg.getType());
  63 + ctx.tellNext(startMsg, SUCCESS);
  64 + }, endMsg -> log.trace("Transaction ended successfully...[{}][{}]", endMsg.getId(), endMsg.getType()),
  65 + throwable -> {
  66 + log.error("Transaction failed! [{}][{}]", tbMsg.getId(), tbMsg.getType(), throwable);
  67 + ctx.tellFailure(tbMsg, throwable);
  68 + });
  69 + }
  70 +
  71 + @Override
  72 + public void destroy() {
  73 +
  74 + }
  75 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.transaction;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
  20 +import org.thingsboard.rule.engine.api.RuleNode;
  21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.api.TbNode;
  23 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  24 +import org.thingsboard.rule.engine.api.TbNodeException;
  25 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  26 +import org.thingsboard.server.common.data.plugin.ComponentType;
  27 +import org.thingsboard.server.common.msg.TbMsg;
  28 +
  29 +import java.util.concurrent.ExecutionException;
  30 +
  31 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
  32 +
  33 +@Slf4j
  34 +@RuleNode(
  35 + type = ComponentType.ACTION,
  36 + name = "transaction end",
  37 + configClazz = EmptyNodeConfiguration.class,
  38 + nodeDescription = "",
  39 + nodeDetails = "",
  40 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  41 + configDirective = ("tbNodeEmptyConfig")
  42 +)
  43 +public class TbTransactionEndNode implements TbNode {
  44 +
  45 + private EmptyNodeConfiguration config;
  46 +
  47 + @Override
  48 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  49 + this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class);
  50 + }
  51 +
  52 + @Override
  53 + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
  54 + ctx.getRuleChainTransactionService().endTransaction(msg,
  55 + successMsg -> ctx.tellNext(successMsg, SUCCESS),
  56 + throwable -> ctx.tellFailure(msg, throwable));
  57 + log.trace("Msg left transaction - [{}][{}]", msg.getId(), msg.getType());
  58 + }
  59 +
  60 + @Override
  61 + public void destroy() {
  62 +
  63 + }
  64 +}
... ...