Commit fa27fd42c70a0e8de244a9cc391391c3acdeb16f
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
15 changed files
with
564 additions
and
11 deletions
@@ -34,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value; | @@ -34,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value; | ||
34 | import org.springframework.context.annotation.Lazy; | 34 | import org.springframework.context.annotation.Lazy; |
35 | import org.springframework.stereotype.Component; | 35 | import org.springframework.stereotype.Component; |
36 | import org.thingsboard.rule.engine.api.MailService; | 36 | import org.thingsboard.rule.engine.api.MailService; |
37 | +import org.thingsboard.rule.engine.api.RuleChainTransactionService; | ||
37 | import org.thingsboard.server.actors.service.ActorService; | 38 | import org.thingsboard.server.actors.service.ActorService; |
38 | import org.thingsboard.server.common.data.DataConstants; | 39 | import org.thingsboard.server.common.data.DataConstants; |
39 | import org.thingsboard.server.common.data.Event; | 40 | import org.thingsboard.server.common.data.Event; |
@@ -222,6 +223,11 @@ public class ActorSystemContext { | @@ -222,6 +223,11 @@ public class ActorSystemContext { | ||
222 | @Getter | 223 | @Getter |
223 | private RuleEngineTransportService ruleEngineTransportService; | 224 | private RuleEngineTransportService ruleEngineTransportService; |
224 | 225 | ||
226 | + @Lazy | ||
227 | + @Autowired | ||
228 | + @Getter | ||
229 | + private RuleChainTransactionService ruleChainTransactionService; | ||
230 | + | ||
225 | @Value("${cluster.partition_id}") | 231 | @Value("${cluster.partition_id}") |
226 | @Getter | 232 | @Getter |
227 | private long queuePartitionId; | 233 | private long queuePartitionId; |
@@ -20,6 +20,7 @@ import com.datastax.driver.core.utils.UUIDs; | @@ -20,6 +20,7 @@ import com.datastax.driver.core.utils.UUIDs; | ||
20 | import org.springframework.util.StringUtils; | 20 | import org.springframework.util.StringUtils; |
21 | import org.thingsboard.rule.engine.api.ListeningExecutor; | 21 | import org.thingsboard.rule.engine.api.ListeningExecutor; |
22 | import org.thingsboard.rule.engine.api.MailService; | 22 | import org.thingsboard.rule.engine.api.MailService; |
23 | +import org.thingsboard.rule.engine.api.RuleChainTransactionService; | ||
23 | import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; | 24 | import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; |
24 | import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; | 25 | import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; |
25 | import org.thingsboard.rule.engine.api.RuleEngineRpcService; | 26 | import org.thingsboard.rule.engine.api.RuleEngineRpcService; |
@@ -124,7 +125,7 @@ class DefaultTbContext implements TbContext { | @@ -124,7 +125,7 @@ class DefaultTbContext implements TbContext { | ||
124 | 125 | ||
125 | @Override | 126 | @Override |
126 | public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { | 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 | @Override | 131 | @Override |
@@ -233,6 +234,11 @@ class DefaultTbContext implements TbContext { | @@ -233,6 +234,11 @@ class DefaultTbContext implements TbContext { | ||
233 | } | 234 | } |
234 | 235 | ||
235 | @Override | 236 | @Override |
237 | + public RuleChainTransactionService getRuleChainTransactionService() { | ||
238 | + return mainCtx.getRuleChainTransactionService(); | ||
239 | + } | ||
240 | + | ||
241 | + @Override | ||
236 | public MailService getMailService() { | 242 | public MailService getMailService() { |
237 | if (mainCtx.isAllowSystemMailService()) { | 243 | if (mainCtx.isAllowSystemMailService()) { |
238 | return mainCtx.getMailService(); | 244 | return mainCtx.getMailService(); |
@@ -56,12 +56,8 @@ import scala.concurrent.duration.Duration; | @@ -56,12 +56,8 @@ import scala.concurrent.duration.Duration; | ||
56 | 56 | ||
57 | import javax.annotation.PostConstruct; | 57 | import javax.annotation.PostConstruct; |
58 | import javax.annotation.PreDestroy; | 58 | import javax.annotation.PreDestroy; |
59 | - | ||
60 | -import java.util.Arrays; | ||
61 | -import java.util.UUID; | ||
62 | import java.util.concurrent.Executors; | 59 | import java.util.concurrent.Executors; |
63 | import java.util.concurrent.ScheduledExecutorService; | 60 | import java.util.concurrent.ScheduledExecutorService; |
64 | -import java.util.concurrent.TimeUnit; | ||
65 | 61 | ||
66 | import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE; | 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,6 +231,9 @@ public class DefaultActorService implements ActorService { | ||
235 | case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE: | 231 | case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE: |
236 | actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray()); | 232 | actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray()); |
237 | break; | 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,8 +19,6 @@ import org.thingsboard.server.common.data.Device; | ||
19 | import org.thingsboard.server.common.data.id.DeviceId; | 19 | import org.thingsboard.server.common.data.id.DeviceId; |
20 | import org.thingsboard.server.common.msg.cluster.ServerAddress; | 20 | import org.thingsboard.server.common.msg.cluster.ServerAddress; |
21 | 21 | ||
22 | -import java.util.Optional; | ||
23 | - | ||
24 | /** | 22 | /** |
25 | * Created by ashvayka on 01.05.18. | 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 | +} |
application/src/main/java/org/thingsboard/server/service/transaction/TbTransactionTask.java
0 → 100644
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,6 +60,7 @@ enum MessageType { | ||
60 | CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE = 12; | 60 | CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE = 12; |
61 | 61 | ||
62 | CLUSTER_DEVICE_STATE_SERVICE_MESSAGE = 13; | 62 | CLUSTER_DEVICE_STATE_SERVICE_MESSAGE = 13; |
63 | + CLUSTER_TRANSACTION_SERVICE_MESSAGE = 14; | ||
63 | } | 64 | } |
64 | 65 | ||
65 | // Messages related to CLUSTER_TELEMETRY_MESSAGE | 66 | // Messages related to CLUSTER_TELEMETRY_MESSAGE |
@@ -142,3 +143,11 @@ message DeviceStateServiceMsgProto { | @@ -142,3 +143,11 @@ message DeviceStateServiceMsgProto { | ||
142 | bool updated = 6; | 143 | bool updated = 6; |
143 | bool deleted = 7; | 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,6 +213,11 @@ actors: | ||
213 | node: | 213 | node: |
214 | # Errors for particular actor are persisted once per specified amount of milliseconds | 214 | # Errors for particular actor are persisted once per specified amount of milliseconds |
215 | error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}" | 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 | statistics: | 221 | statistics: |
217 | # Enable/disable actor statistics | 222 | # Enable/disable actor statistics |
218 | enabled: "${ACTORS_STATISTICS_ENABLED:true}" | 223 | enabled: "${ACTORS_STATISTICS_ENABLED:true}" |
@@ -310,7 +315,7 @@ spring: | @@ -310,7 +315,7 @@ spring: | ||
310 | password: "${SPRING_DATASOURCE_PASSWORD:}" | 315 | password: "${SPRING_DATASOURCE_PASSWORD:}" |
311 | 316 | ||
312 | # PostgreSQL DAO Configuration | 317 | # PostgreSQL DAO Configuration |
313 | -# spring: | 318 | +#spring: |
314 | # data: | 319 | # data: |
315 | # sql: | 320 | # sql: |
316 | # repositories: | 321 | # repositories: |
@@ -41,6 +41,7 @@ public final class TbMsg implements Serializable { | @@ -41,6 +41,7 @@ public final class TbMsg implements Serializable { | ||
41 | private final TbMsgMetaData metaData; | 41 | private final TbMsgMetaData metaData; |
42 | private final TbMsgDataType dataType; | 42 | private final TbMsgDataType dataType; |
43 | private final String data; | 43 | private final String data; |
44 | + private final TbMsgTransactionData transactionData; | ||
44 | 45 | ||
45 | //The following fields are not persisted to DB, because they can always be recovered from the context; | 46 | //The following fields are not persisted to DB, because they can always be recovered from the context; |
46 | private final RuleChainId ruleChainId; | 47 | private final RuleChainId ruleChainId; |
@@ -55,11 +56,17 @@ public final class TbMsg implements Serializable { | @@ -55,11 +56,17 @@ public final class TbMsg implements Serializable { | ||
55 | this.metaData = metaData; | 56 | this.metaData = metaData; |
56 | this.data = data; | 57 | this.data = data; |
57 | this.dataType = TbMsgDataType.JSON; | 58 | this.dataType = TbMsgDataType.JSON; |
59 | + this.transactionData = new TbMsgTransactionData(id, originator); | ||
58 | this.ruleChainId = ruleChainId; | 60 | this.ruleChainId = ruleChainId; |
59 | this.ruleNodeId = ruleNodeId; | 61 | this.ruleNodeId = ruleNodeId; |
60 | this.clusterPartition = clusterPartition; | 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 | public static ByteBuffer toBytes(TbMsg msg) { | 70 | public static ByteBuffer toBytes(TbMsg msg) { |
64 | MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); | 71 | MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); |
65 | builder.setId(msg.getId().toString()); | 72 | builder.setId(msg.getId().toString()); |
@@ -82,6 +89,16 @@ public final class TbMsg implements Serializable { | @@ -82,6 +89,16 @@ public final class TbMsg implements Serializable { | ||
82 | builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build()); | 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 | builder.setDataType(msg.getDataType().ordinal()); | 102 | builder.setDataType(msg.getDataType().ordinal()); |
86 | builder.setData(msg.getData()); | 103 | builder.setData(msg.getData()); |
87 | byte[] bytes = builder.build().toByteArray(); | 104 | byte[] bytes = builder.build().toByteArray(); |
@@ -92,6 +109,9 @@ public final class TbMsg implements Serializable { | @@ -92,6 +109,9 @@ public final class TbMsg implements Serializable { | ||
92 | try { | 109 | try { |
93 | MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array()); | 110 | MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array()); |
94 | TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap()); | 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 | EntityId entityId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())); | 115 | EntityId entityId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())); |
96 | RuleChainId ruleChainId = new RuleChainId(new UUID(proto.getRuleChainIdMSB(), proto.getRuleChainIdLSB())); | 116 | RuleChainId ruleChainId = new RuleChainId(new UUID(proto.getRuleChainIdMSB(), proto.getRuleChainIdLSB())); |
97 | RuleNodeId ruleNodeId = null; | 117 | RuleNodeId ruleNodeId = null; |
@@ -99,7 +119,7 @@ public final class TbMsg implements Serializable { | @@ -99,7 +119,7 @@ public final class TbMsg implements Serializable { | ||
99 | ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); | 119 | ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); |
100 | } | 120 | } |
101 | TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; | 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 | } catch (InvalidProtocolBufferException e) { | 123 | } catch (InvalidProtocolBufferException e) { |
104 | throw new IllegalStateException("Could not parse protobuf for TbMsg", e); | 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,6 +23,13 @@ message TbMsgMetaDataProto { | ||
23 | map<string, string> data = 1; | 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 | message TbMsgProto { | 33 | message TbMsgProto { |
27 | string id = 1; | 34 | string id = 1; |
28 | string type = 2; | 35 | string type = 2; |
@@ -39,7 +46,9 @@ message TbMsgProto { | @@ -39,7 +46,9 @@ message TbMsgProto { | ||
39 | 46 | ||
40 | TbMsgMetaDataProto metaData = 11; | 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 | } |
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,4 +103,6 @@ public interface TbContext { | ||
103 | ScriptEngine createJsScriptEngine(String script, String... argNames); | 103 | ScriptEngine createJsScriptEngine(String script, String... argNames); |
104 | 104 | ||
105 | String getNodeId(); | 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 | +} |