From 5c35a858bb7c63e5eb32e73d1d7991ee9d3b465c Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 23 Nov 2023 15:39:02 +0200 Subject: [PATCH 01/31] [WIP] Initial implementation for "first and last" strategy in transport service --- .../service/DefaultTransportService.java | 239 +++++++++++++++++- 1 file changed, 230 insertions(+), 9 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 37b77a5268..0ece919d7b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -52,7 +52,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.limit.LimitedApi; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -96,9 +95,9 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.AsyncCallbackTemplate; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.discovery.TopicService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; @@ -113,6 +112,7 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Random; import java.util.Set; @@ -125,6 +125,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; /** @@ -139,7 +141,7 @@ public class DefaultTransportService implements TransportService { public static final String SESSION_EXPIRED_MESSAGE = "Session has expired due to last activity time!"; public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_OPEN = getSessionEventMsg(TransportProtos.SessionEvent.OPEN); public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_CLOSED = getSessionEventMsg(TransportProtos.SessionEvent.CLOSED); - public static final TransportProtos.SessionCloseNotificationProto SESSION_CLOSE_NOTIFICATION_PROTO = TransportProtos.SessionCloseNotificationProto.newBuilder() + public static final TransportProtos.SessionCloseNotificationProto SESSION_EXPIRED_NOTIFICATION_PROTO = TransportProtos.SessionCloseNotificationProto.newBuilder() .setMessage(SESSION_EXPIRED_MESSAGE).build(); public static final TransportProtos.SubscribeToAttributeUpdatesMsg SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG = TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder() .setSessionType(TransportProtos.SessionType.ASYNC).build(); @@ -202,7 +204,7 @@ public class DefaultTransportService implements TransportService { private ExecutorService mainConsumerExecutor; public final ConcurrentMap sessions = new ConcurrentHashMap<>(); - private final ConcurrentMap sessionsActivity = new ConcurrentHashMap<>(); + private final ActivityStateManager activityStateManager; private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); private volatile boolean stopped = false; @@ -234,6 +236,7 @@ public class DefaultTransportService implements TransportService { this.eventPublisher = eventPublisher; this.notificationRuleProcessor = notificationRuleProcessor; this.entityLimitsCache = entityLimitsCache; + activityStateManager = new ActivityStateManager(); } @PostConstruct @@ -242,7 +245,7 @@ public class DefaultTransportService implements TransportService { this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); this.transportCallbackExecutor = ThingsBoardExecutors.newWorkStealingPool(20, getClass()); - this.scheduler.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); + activityStateManager.init(); this.scheduler.scheduleAtFixedRate(this::invalidateRateLimits, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); transportApiRequestTemplate = queueProvider.createTransportApiRequestTemplate(); transportApiRequestTemplate.setMessagesStats(transportApiStats); @@ -785,11 +788,10 @@ public class DefaultTransportService implements TransportService { } private void reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { - UUID sessionId = toSessionId(sessionInfo); - SessionActivityData sessionMetaData = sessionsActivity.computeIfAbsent(sessionId, id -> new SessionActivityData(sessionInfo)); - sessionMetaData.updateLastActivityTime(); + activityStateManager.recordActivity(sessionInfo); } + /* private void checkInactivityAndReportActivity() { long expTime = System.currentTimeMillis() - sessionInactivityTimeout; Set sessionsToRemove = new HashSet<>(); @@ -819,7 +821,7 @@ public class DefaultTransportService implements TransportService { sessions.remove(uuid); sessionsToRemove.add(uuid); process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - sessionMD.getListener().onRemoteSessionCloseCommand(uuid, SESSION_CLOSE_NOTIFICATION_PROTO); + sessionMD.getListener().onRemoteSessionCloseCommand(uuid, SESSION_EXPIRED_NOTIFICATION_PROTO); } } else { if (lastActivityTime > sessionAD.getLastReportedActivityTime()) { @@ -844,6 +846,225 @@ public class DefaultTransportService implements TransportService { // Removes all closed or short-lived sessions. sessionsToRemove.forEach(sessionsActivity::remove); } + */ + + + // TODO: how can I get access to sessions if activity management logic is implemented as a separate service (class) + + // TODO: why sessions and session activities are managed in a separate maps? maybe it is better to manage in a single map + // (eg. we can check if we had sent last activity event when deregistering session) + + // TODO: currently if activity event is received between precise session expiration time and reportLastEventAndStartNewPeriod() call + // we will "resurrect" this session meaning that + + // TODO: I optimistically set alreadyBeenReported status to true to avoid reporting several activity events if they arrive in rapid succession + // this can cause lost activity updates if first event that got reported failed to enqueue in Kafka and next reporting period is already started + // (maybe having a queue of "first" events will help with this, queue will be cleared once event was successfully queued or later time was reported by event in next period) + // setting alreadyBeenReported status in callbacks ensures that events are reported but can cause several "first" events to be reported + + // TODO: currently activity states are reported on per session basis, but one physical device can have several sessions + // maybe it is a good idea to manage activity state on per device basis + + private final class ActivityStateManager { + + private final ConcurrentMap sessionActivityStates = new ConcurrentHashMap<>(); + + @lombok.Value + // TODO: I chose to have immutable objects for concurrency considerations, + // but it can possibly create a performance issue with creating large amounts of new objects + // maybe mutable objects with volatile fields is better + private class ActivityState { + + TransportProtos.SessionInfoProto sessionInfo; + long lastActivityTime; + boolean alreadyBeenReported; + long lastReportedTime; + + ActivityState withSessionInfo(TransportProtos.SessionInfoProto sessionInfo) { + return new ActivityState(sessionInfo, getLastActivityTime(), isAlreadyBeenReported(), getLastReportedTime()); + } + + ActivityState withLastActivityTime(long lastActivityTime) { + return new ActivityState(getSessionInfo(), lastActivityTime, isAlreadyBeenReported(), getLastReportedTime()); + } + + ActivityState withReportedStatus(boolean hasAlreadyBeenReported) { + return new ActivityState(getSessionInfo(), getLastActivityTime(), hasAlreadyBeenReported, getLastReportedTime()); + } + + ActivityState withLastReportedTime(long lastReportedTime) { + return new ActivityState(getSessionInfo(), getLastActivityTime(), isAlreadyBeenReported(), lastReportedTime); + } + + } + + // TODO: maybe CAS-based synchronization policy will perform better? + // locks can put threads to sleep which introduces scheduling overhead (it is up to JVM to device if a thread will go to sleep or will be spin-waiting) + // activity management tasks are short lived so scheduling overhead may be significant, so CAS can be better since it does not put threads to sleep (always spin-waiting) + // Compare ReadWriteLock to CAS + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final AtomicInteger currentPeriodId = new AtomicInteger(); + + private void init() { + scheduler.scheduleAtFixedRate(this::reportLastEventAndStartNewPeriod, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); + } + + private void recordActivity(TransportProtos.SessionInfoProto sessionInfo) { + long newLastActivityTime = System.currentTimeMillis(); + var sessionId = toSessionId(sessionInfo); + lock.readLock().lock(); + try { + sessionActivityStates.compute(sessionId, (id, currentActivityState) -> { + log.info("------------------------------------------------------------------------------------------------------------------------"); + log.info("Record activity: entered compute! Session id: [{}]", sessionId); + var activityState = Objects.requireNonNullElseGet(currentActivityState, () -> new ActivityState(sessionInfo, newLastActivityTime, false, 0L)); + if (activityState.isAlreadyBeenReported()) { // update the last activity time + log.info("------------------------------------------------------------------------------------------------------------------------"); + return activityState.withLastActivityTime(newLastActivityTime); + } + int capturedPeriodId = currentPeriodId.get(); + reportActivityStateToCore(activityState.getSessionInfo(), newLastActivityTime, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + // Success, the optimistic assumption was correct: update last reported time if it is newer + log.info("Record activity: successful callback received! Session id: [{}]", sessionId); + lock.readLock().lock(); + try { + updateLastReportedTime(sessionId, newLastActivityTime); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void onError(Throwable e) { + // Error: revert alreadyBeenReported to false + log.info("Record activity: failed callback received! Session id: [{}]", sessionId); + log.debug("[{}] Failed to report last activity time!", sessionId, e); + lock.readLock().lock(); + try { + sessionActivityStates.computeIfPresent(sessionId, (__, activityState) -> { + boolean updatedReportedStatus = activityState.isAlreadyBeenReported(); + if (capturedPeriodId == currentPeriodId.get() && activityState.getLastReportedTime() < newLastActivityTime) { + updatedReportedStatus = false; + } + return activityState.withReportedStatus(updatedReportedStatus); + }); + } finally { + lock.readLock().unlock(); + } + } + }); + log.info("------------------------------------------------------------------------------------------------------------------------"); + // Optimistically set hasAlreadyBeenReported to true + return new ActivityState(sessionInfo, newLastActivityTime, true, activityState.getLastReportedTime()); + }); + } finally { + lock.readLock().unlock(); + } + } + + private void reportLastEventAndStartNewPeriod() { + lock.writeLock().lock(); + // log.info("------------------------------------------------------------------------------------------------------------------------"); + // log.info("Period change start! Current period id: [{}], activity states: {}", currentPeriodId.get(), sessionActivityStates.keySet()); + try { + Set activityStatesToRemove = new HashSet<>(); + long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; + for (Map.Entry entry : sessionActivityStates.entrySet()) { + var sessionId = entry.getKey(); + var activityState = entry.getValue(); + + long lastActivityTime = activityState.getLastActivityTime(); + + // TODO: why this update is needed? if gateway session is present but this session is already gone we will not check if gateway's last activity time is greater than this one + SessionMetaData sessionMetaData = sessions.get(sessionId); + if (sessionMetaData != null) { + activityState = activityState.withSessionInfo(sessionMetaData.getSessionInfo()); + entry.setValue(activityState); + } else { + // log.info("Removing activity state! Session id: [{}]", sessionId); + activityStatesToRemove.add(sessionId); + } + // TODO: ask about this part end + + TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfo(); + + if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { + var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); + SessionMetaData gwSessionMetaData = sessions.get(gwSessionId); + if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { + ActivityState gwActivityState = sessionActivityStates.get(gwSessionId); + lastActivityTime = Math.max(gwActivityState.getLastActivityTime(), lastActivityTime); + } + } + + if (sessionMetaData != null && lastActivityTime < expirationTime) { + // log.info("Session has expired! Session id: [{}]", sessionId); + if (log.isDebugEnabled()) { + log.debug("[{}] Session has expired due to last activity time: {}!", sessionId, lastActivityTime); + } + sessions.remove(sessionId); + activityStatesToRemove.add(sessionId); + process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); + sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); + } else if (activityState.getLastReportedTime() < lastActivityTime) { + long finalLastActivityTime = lastActivityTime; + reportActivityStateToCore(sessionInfo, sessionMetaData, lastActivityTime, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + lock.readLock().lock(); + try { + updateLastReportedTime(sessionId, finalLastActivityTime); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void onError(Throwable e) { + log.debug("[{}] Failed to report last activity time!", sessionId, e); + } + }); + } + entry.setValue(activityState.withReportedStatus(false)); + } + activityStatesToRemove.forEach(sessionActivityStates::remove); + } finally { + currentPeriodId.incrementAndGet(); + // log.info("Period change end! Current period id: [{}], activity states {}", currentPeriodId.get(), sessionActivityStates.keySet()); + // log.info("------------------------------------------------------------------------------------------------------------------------"); + lock.writeLock().unlock(); + } + } + + private void reportActivityStateToCore(TransportProtos.SessionInfoProto sessionInfo, long lastActivityTime, TransportServiceCallback msgAcknowledgedCallback) { + SessionMetaData sessionMetaData = sessions.get(toSessionId(sessionInfo)); + reportActivityStateToCore(sessionInfo, sessionMetaData, lastActivityTime, msgAcknowledgedCallback); + } + + private void reportActivityStateToCore( + TransportProtos.SessionInfoProto sessionInfo, SessionMetaData sessionMetaData, long lastActivityTime, TransportServiceCallback msgAcknowledgedCallback + ) { + log.info("Reporting activity state to core! Session id: [{}], last activity time: [{}], current period id: [{}]", + toSessionId(sessionInfo), lastActivityTime, currentPeriodId.get()); + TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) + .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) + .setLastActivityTime(lastActivityTime) + .build(); + process(sessionInfo, subscriptionInfo, msgAcknowledgedCallback); + } + + private void updateLastReportedTime(UUID sessionId, long lastActivityTime) { + sessionActivityStates.computeIfPresent(sessionId, (__, currentActivityState) -> { + long updatedLastReportedTime = Math.max(currentActivityState.getLastReportedTime(), lastActivityTime); + return currentActivityState.withLastReportedTime(updatedLastReportedTime); + }); + } + + } + @Override public void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error) { From e2023722ce23ee9860373454e98af37fbaa4350a Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 23 Nov 2023 15:39:51 +0200 Subject: [PATCH 02/31] [WIP] Remove session activity data class --- .../service/SessionActivityData.java | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionActivityData.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionActivityData.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionActivityData.java deleted file mode 100644 index 3e0be50ce6..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionActivityData.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2024 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.Data; -import org.thingsboard.server.gen.transport.TransportProtos; - -/** - * Created by ashvayka on 15.10.18. - */ -@Data -public class SessionActivityData { - - private volatile TransportProtos.SessionInfoProto sessionInfo; - private volatile long lastActivityTime; - private volatile long lastReportedActivityTime; - - SessionActivityData(TransportProtos.SessionInfoProto sessionInfo) { - this.sessionInfo = sessionInfo; - } - - void updateLastActivityTime() { - this.lastActivityTime = System.currentTimeMillis(); - } - -} From 114d2c3c1cee3128329ca1616a76756fde056cba Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Wed, 6 Dec 2023 19:08:28 +0200 Subject: [PATCH 03/31] [WIP] Initial implementation --- .../transport/activity/ActivityState.java | 41 ++ .../activity/ActivityStateManager.java | 43 ++ .../activity/ActivityStateManagerImpl.java | 240 +++++++++++ .../activity/ActivityStateReportCallback.java | 41 ++ .../activity/AsyncActivityStateReporter.java | 41 ++ .../service/DefaultTransportService.java | 395 +++++------------- .../service/TransportActivityState.java | 44 ++ 7 files changed, 562 insertions(+), 283 deletions(-) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManager.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AsyncActivityStateReporter.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityState.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java new file mode 100644 index 0000000000..09285f9192 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java @@ -0,0 +1,41 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity; + +import lombok.Data; + +@Data +public class ActivityState { + + private volatile long lastRecordedTime; + private volatile long lastReportedTime; + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManager.java new file mode 100644 index 0000000000..db24c2388d --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManager.java @@ -0,0 +1,43 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity; + +import java.util.function.Supplier; + +public interface ActivityStateManager { + + void init(); + + void recordActivity(Key key, Supplier newActivityStateSupplier); + + void destroy(); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java new file mode 100644 index 0000000000..7d87b02f19 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java @@ -0,0 +1,240 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.data.util.Pair; +import org.thingsboard.common.util.ThingsBoardThreadFactory; + +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +@Slf4j +public final class ActivityStateManagerImpl implements ActivityStateManager { + + private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); + + @Data + private static class ActivityStateWrapper { + + volatile ActivityState activityState; + volatile boolean alreadyBeenReported; + + } + + private ScheduledExecutorService scheduler; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private final AtomicInteger currentPeriodId = new AtomicInteger(); + private final AsyncActivityStateReporter reporter; + private final long reportingPeriodDurationMillis; + private final String name; + private boolean initialized; + + public ActivityStateManagerImpl(AsyncActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { + this.reporter = Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided reporter is null."); + this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation + this.name = name == null ? "activity-state-manager" : name; + } + + @Override + public synchronized void init() { + if (!initialized) { + initialized = true; + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); + scheduler.scheduleAtFixedRate(this::reportLastEventAndStartNewPeriod, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); + } + } + + @Override + public void recordActivity(Key activityKey, Supplier newActivityStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + int capturedPeriodId = currentPeriodId.get(); + lock.readLock().lock(); + SettableFuture> reportCompletedFuture = SettableFuture.create(); + try { + activityStates.compute(activityKey, (key, activityStateWrapper) -> { + if (activityStateWrapper == null) { + State activityState = newActivityStateSupplier.get(); + activityState.setLastRecordedTime(newLastRecordedTime); + activityState.setLastReportedTime(0L); + activityStateWrapper = new ActivityStateWrapper(); + activityStateWrapper.setActivityState(activityState); + activityStateWrapper.setAlreadyBeenReported(false); + } else { + activityStateWrapper.getActivityState().setLastRecordedTime(newLastRecordedTime); + } + if (activityStateWrapper.isAlreadyBeenReported()) { + return activityStateWrapper; + } + var activityState = activityStateWrapper.getActivityState(); + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + reporter.reportAsync(key, (State) activityState, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(Key key, long reportedTime) { + reportCompletedFuture.set(Pair.of(key, reportedTime)); + } + + @Override + public void onFailure(Key key, Throwable t) { + reportCompletedFuture.setException(t); + } + + @Override + public void onRemove(Key key) { + lock.readLock().lock(); + try { + activityStates.remove(key); + } finally { + lock.readLock().unlock(); + } + } + }); + } + activityStateWrapper.setAlreadyBeenReported(true); + return activityStateWrapper; + }); + } finally { + lock.readLock().unlock(); + } + Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { + @Override + public void onSuccess(Pair reportResult) { + lock.readLock().lock(); + try { + updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void onFailure(@NonNull Throwable t) { // TODO: add failure logging + lock.readLock().lock(); + try { + rollbackReportedStatus(activityKey, capturedPeriodId, newLastRecordedTime); + } finally { + lock.readLock().unlock(); + } + } + }, MoreExecutors.directExecutor()); + } + + private void rollbackReportedStatus(Key key, int capturedPeriodId, long newLastRecordedTime) { + activityStates.computeIfPresent(key, (__, activityStateWrapper) -> { + if (capturedPeriodId == currentPeriodId.get() && activityStateWrapper.getActivityState().getLastReportedTime() < newLastRecordedTime) { + activityStateWrapper.setAlreadyBeenReported(false); + } + return activityStateWrapper; + }); + } + + private void reportLastEventAndStartNewPeriod() { + lock.writeLock().lock(); + try { + Map activityStateMap = activityStates.entrySet().stream() + .peek(entry -> entry.getValue().setAlreadyBeenReported(false)) + .collect(Collectors.toMap(Map.Entry::getKey, entry -> (State) entry.getValue().getActivityState())); + reporter.reportAsync(activityStateMap, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(Key key, long reportedTime) { + lock.readLock().lock(); + try { + updateLastReportedTime(key, reportedTime); + } finally { + lock.readLock().unlock(); + } + } + + @Override + public void onFailure(Key key, Throwable t) { + log.debug("Failed to report activity state for key [{}]!", key, t); + } + + @Override + public void onRemove(Key key) { + lock.readLock().lock(); + try { + activityStates.remove(key); + } finally { + lock.readLock().unlock(); + } + } + }); + } finally { + currentPeriodId.incrementAndGet(); + lock.writeLock().unlock(); + } + } + + private void updateLastReportedTime(Key key, long newLastReportedTime) { + activityStates.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getActivityState(); + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityStateWrapper; + }); + } + + @Override + public synchronized void destroy() { + if (initialized) { + initialized = false; + if (scheduler != null) { + scheduler.shutdown(); + try { + if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java new file mode 100644 index 0000000000..d9d897d4a1 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java @@ -0,0 +1,41 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity; + +public interface ActivityStateReportCallback { + + void onSuccess(Key key, long reportedTime); + + void onFailure(Key key, Throwable t); + + void onRemove(Key key); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AsyncActivityStateReporter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AsyncActivityStateReporter.java new file mode 100644 index 0000000000..ab9094c252 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AsyncActivityStateReporter.java @@ -0,0 +1,41 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity; + +import java.util.Map; + +public interface AsyncActivityStateReporter { + + void reportAsync(Key key, State activityState, ActivityStateReportCallback reportCallback); + + void reportAsync(Map activityStates, ActivityStateReportCallback reportCallback); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 0ece919d7b..7769762b84 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -72,6 +72,10 @@ import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportTenantProfileCache; +import org.thingsboard.server.common.transport.activity.ActivityStateManager; +import org.thingsboard.server.common.transport.activity.ActivityStateManagerImpl; +import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; +import org.thingsboard.server.common.transport.activity.AsyncActivityStateReporter; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; @@ -108,14 +112,11 @@ import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.Random; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -125,8 +126,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; /** @@ -204,7 +203,7 @@ public class DefaultTransportService implements TransportService { private ExecutorService mainConsumerExecutor; public final ConcurrentMap sessions = new ConcurrentHashMap<>(); - private final ActivityStateManager activityStateManager; + private ActivityStateManager activityStateManager; private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); private volatile boolean stopped = false; @@ -236,7 +235,6 @@ public class DefaultTransportService implements TransportService { this.eventPublisher = eventPublisher; this.notificationRuleProcessor = notificationRuleProcessor; this.entityLimitsCache = entityLimitsCache; - activityStateManager = new ActivityStateManager(); } @PostConstruct @@ -245,7 +243,6 @@ public class DefaultTransportService implements TransportService { this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); this.transportCallbackExecutor = ThingsBoardExecutors.newWorkStealingPool(20, getClass()); - activityStateManager.init(); this.scheduler.scheduleAtFixedRate(this::invalidateRateLimits, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); transportApiRequestTemplate = queueProvider.createTransportApiRequestTemplate(); transportApiRequestTemplate.setMessagesStats(transportApiStats); @@ -256,8 +253,108 @@ public class DefaultTransportService implements TransportService { transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); + activityStateManager = new ActivityStateManagerImpl<>(reporter, sessionReportTimeout, "transport-activity-state-manager"); + activityStateManager.init(); } + // TODO: why sessions and session activities are managed in a separate maps? maybe it is better to manage in a single map + // (eg. we can check if we had sent last activity event when deregistering session) + + // TODO: currently if activity event is received between precise session expiration time and reportLastEventAndStartNewPeriod() call + // we will "resurrect" this session meaning that it will be considered alive but in fact it did not perform any activity for more than timeout allows + + // TODO: I optimistically set alreadyBeenReported status to true to avoid reporting several activity events if they arrive in rapid succession + // this can cause lost activity updates if first event that got reported failed to enqueue in Kafka and next reporting period is already started + // (maybe having a queue of "first" events will help with this, queue will be cleared once event was successfully queued or later time was reported by event in next period) + // setting alreadyBeenReported status in callbacks ensures that events are reported but can cause several "first" events to be reported + private final AsyncActivityStateReporter reporter = new AsyncActivityStateReporter<>() { + @Override + public void reportAsync(UUID sessionId, TransportActivityState activityState, ActivityStateReportCallback reportCallback) { + SessionMetaData sessionMetaData = sessions.get(sessionId); + TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) + .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) + .setLastActivityTime(activityState.getLastRecordedTime()) + .build(); + process(activityState.getSessionInfoProto(), subscriptionInfo, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + reportCallback.onSuccess(sessionId, activityState.getLastRecordedTime()); + } + + @Override + public void onError(Throwable e) { + reportCallback.onFailure(sessionId, e); + } + }); + } + + @Override + public void reportAsync(Map activityStates, ActivityStateReportCallback reportCallback) { + long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; + for (Map.Entry entry : activityStates.entrySet()) { + var sessionId = entry.getKey(); + var activityState = entry.getValue(); + + long lastActivityTime = activityState.getLastRecordedTime(); + + SessionMetaData sessionMetaData = sessions.get(sessionId); + if (sessionMetaData != null) { + activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); + } else { + reportCallback.onRemove(sessionId); + } + + TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); + + if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { + var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); + SessionMetaData gwSessionMetaData = sessions.get(gwSessionId); + if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { + TransportActivityState gwActivityState = activityStates.get(gwSessionId); + if (gwActivityState != null) { + lastActivityTime = Math.max(gwActivityState.getLastRecordedTime(), lastActivityTime); + } + } + } + + if (sessionMetaData != null && lastActivityTime < expirationTime) { + if (log.isDebugEnabled()) { + log.debug("[{}] Session has expired due to last activity time: {}!", sessionId, lastActivityTime); + } + sessions.remove(sessionId); + reportCallback.onRemove(sessionId); + process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); + sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); + } else if (activityState.getLastReportedTime() < lastActivityTime) { + long finalLastActivityTime = lastActivityTime; + reportActivityStateToCore(sessionInfo, sessionMetaData, lastActivityTime, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + reportCallback.onSuccess(sessionId, finalLastActivityTime); + } + + @Override + public void onError(Throwable e) { + reportCallback.onFailure(sessionId, e); + } + }); + } + } + } + + private void reportActivityStateToCore( + TransportProtos.SessionInfoProto sessionInfo, SessionMetaData sessionMetaData, long lastActivityTime, TransportServiceCallback callback + ) { + TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) + .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) + .setLastActivityTime(lastActivityTime) + .build(); + process(sessionInfo, subscriptionInfo, callback); + } + }; + @AfterStartUp(order = AfterStartUp.TRANSPORT_SERVICE) public void start() { mainConsumerExecutor.execute(() -> { @@ -309,6 +406,9 @@ public class DefaultTransportService implements TransportService { if (transportApiRequestTemplate != null) { transportApiRequestTemplate.stop(); } + if (activityStateManager != null) { + activityStateManager.destroy(); + } } @Override @@ -788,283 +888,12 @@ public class DefaultTransportService implements TransportService { } private void reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { - activityStateManager.recordActivity(sessionInfo); - } - - /* - private void checkInactivityAndReportActivity() { - long expTime = System.currentTimeMillis() - sessionInactivityTimeout; - Set sessionsToRemove = new HashSet<>(); - sessionsActivity.forEach((uuid, sessionAD) -> { - long lastActivityTime = sessionAD.getLastActivityTime(); - SessionMetaData sessionMD = sessions.get(uuid); - if (sessionMD != null) { - sessionAD.setSessionInfo(sessionMD.getSessionInfo()); - } else { - sessionsToRemove.add(uuid); - } - TransportProtos.SessionInfoProto sessionInfo = sessionAD.getSessionInfo(); - - if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { - var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); - SessionMetaData gwMetaData = sessions.get(gwSessionId); - SessionActivityData gwActivityData = sessionsActivity.get(gwSessionId); - if (gwMetaData != null && gwMetaData.isOverwriteActivityTime()) { - lastActivityTime = Math.max(gwActivityData.getLastActivityTime(), lastActivityTime); - } - } - if (lastActivityTime < expTime) { - if (sessionMD != null) { - if (log.isDebugEnabled()) { - log.debug("[{}] Session has expired due to last activity time: {}", toSessionId(sessionInfo), lastActivityTime); - } - sessions.remove(uuid); - sessionsToRemove.add(uuid); - process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - sessionMD.getListener().onRemoteSessionCloseCommand(uuid, SESSION_EXPIRED_NOTIFICATION_PROTO); - } - } else { - if (lastActivityTime > sessionAD.getLastReportedActivityTime()) { - final long lastActivityTimeFinal = lastActivityTime; - process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(sessionMD != null && sessionMD.isSubscribedToAttributes()) - .setRpcSubscription(sessionMD != null && sessionMD.isSubscribedToRPC()) - .setLastActivityTime(lastActivityTime).build(), new TransportServiceCallback() { - @Override - public void onSuccess(Void msg) { - sessionAD.setLastReportedActivityTime(lastActivityTimeFinal); - } - - @Override - public void onError(Throwable e) { - log.warn("[{}] Failed to report last activity time", uuid, e); - } - }); - } - } + activityStateManager.recordActivity(toSessionId(sessionInfo), () -> { + var activityState = new TransportActivityState(); + activityState.setSessionInfoProto(sessionInfo); + return activityState; }); - // Removes all closed or short-lived sessions. - sessionsToRemove.forEach(sessionsActivity::remove); } - */ - - - // TODO: how can I get access to sessions if activity management logic is implemented as a separate service (class) - - // TODO: why sessions and session activities are managed in a separate maps? maybe it is better to manage in a single map - // (eg. we can check if we had sent last activity event when deregistering session) - - // TODO: currently if activity event is received between precise session expiration time and reportLastEventAndStartNewPeriod() call - // we will "resurrect" this session meaning that - - // TODO: I optimistically set alreadyBeenReported status to true to avoid reporting several activity events if they arrive in rapid succession - // this can cause lost activity updates if first event that got reported failed to enqueue in Kafka and next reporting period is already started - // (maybe having a queue of "first" events will help with this, queue will be cleared once event was successfully queued or later time was reported by event in next period) - // setting alreadyBeenReported status in callbacks ensures that events are reported but can cause several "first" events to be reported - - // TODO: currently activity states are reported on per session basis, but one physical device can have several sessions - // maybe it is a good idea to manage activity state on per device basis - - private final class ActivityStateManager { - - private final ConcurrentMap sessionActivityStates = new ConcurrentHashMap<>(); - - @lombok.Value - // TODO: I chose to have immutable objects for concurrency considerations, - // but it can possibly create a performance issue with creating large amounts of new objects - // maybe mutable objects with volatile fields is better - private class ActivityState { - - TransportProtos.SessionInfoProto sessionInfo; - long lastActivityTime; - boolean alreadyBeenReported; - long lastReportedTime; - - ActivityState withSessionInfo(TransportProtos.SessionInfoProto sessionInfo) { - return new ActivityState(sessionInfo, getLastActivityTime(), isAlreadyBeenReported(), getLastReportedTime()); - } - - ActivityState withLastActivityTime(long lastActivityTime) { - return new ActivityState(getSessionInfo(), lastActivityTime, isAlreadyBeenReported(), getLastReportedTime()); - } - - ActivityState withReportedStatus(boolean hasAlreadyBeenReported) { - return new ActivityState(getSessionInfo(), getLastActivityTime(), hasAlreadyBeenReported, getLastReportedTime()); - } - - ActivityState withLastReportedTime(long lastReportedTime) { - return new ActivityState(getSessionInfo(), getLastActivityTime(), isAlreadyBeenReported(), lastReportedTime); - } - - } - - // TODO: maybe CAS-based synchronization policy will perform better? - // locks can put threads to sleep which introduces scheduling overhead (it is up to JVM to device if a thread will go to sleep or will be spin-waiting) - // activity management tasks are short lived so scheduling overhead may be significant, so CAS can be better since it does not put threads to sleep (always spin-waiting) - // Compare ReadWriteLock to CAS - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final AtomicInteger currentPeriodId = new AtomicInteger(); - - private void init() { - scheduler.scheduleAtFixedRate(this::reportLastEventAndStartNewPeriod, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); - } - - private void recordActivity(TransportProtos.SessionInfoProto sessionInfo) { - long newLastActivityTime = System.currentTimeMillis(); - var sessionId = toSessionId(sessionInfo); - lock.readLock().lock(); - try { - sessionActivityStates.compute(sessionId, (id, currentActivityState) -> { - log.info("------------------------------------------------------------------------------------------------------------------------"); - log.info("Record activity: entered compute! Session id: [{}]", sessionId); - var activityState = Objects.requireNonNullElseGet(currentActivityState, () -> new ActivityState(sessionInfo, newLastActivityTime, false, 0L)); - if (activityState.isAlreadyBeenReported()) { // update the last activity time - log.info("------------------------------------------------------------------------------------------------------------------------"); - return activityState.withLastActivityTime(newLastActivityTime); - } - int capturedPeriodId = currentPeriodId.get(); - reportActivityStateToCore(activityState.getSessionInfo(), newLastActivityTime, new TransportServiceCallback<>() { - @Override - public void onSuccess(Void msgAcknowledged) { - // Success, the optimistic assumption was correct: update last reported time if it is newer - log.info("Record activity: successful callback received! Session id: [{}]", sessionId); - lock.readLock().lock(); - try { - updateLastReportedTime(sessionId, newLastActivityTime); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public void onError(Throwable e) { - // Error: revert alreadyBeenReported to false - log.info("Record activity: failed callback received! Session id: [{}]", sessionId); - log.debug("[{}] Failed to report last activity time!", sessionId, e); - lock.readLock().lock(); - try { - sessionActivityStates.computeIfPresent(sessionId, (__, activityState) -> { - boolean updatedReportedStatus = activityState.isAlreadyBeenReported(); - if (capturedPeriodId == currentPeriodId.get() && activityState.getLastReportedTime() < newLastActivityTime) { - updatedReportedStatus = false; - } - return activityState.withReportedStatus(updatedReportedStatus); - }); - } finally { - lock.readLock().unlock(); - } - } - }); - log.info("------------------------------------------------------------------------------------------------------------------------"); - // Optimistically set hasAlreadyBeenReported to true - return new ActivityState(sessionInfo, newLastActivityTime, true, activityState.getLastReportedTime()); - }); - } finally { - lock.readLock().unlock(); - } - } - - private void reportLastEventAndStartNewPeriod() { - lock.writeLock().lock(); - // log.info("------------------------------------------------------------------------------------------------------------------------"); - // log.info("Period change start! Current period id: [{}], activity states: {}", currentPeriodId.get(), sessionActivityStates.keySet()); - try { - Set activityStatesToRemove = new HashSet<>(); - long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; - for (Map.Entry entry : sessionActivityStates.entrySet()) { - var sessionId = entry.getKey(); - var activityState = entry.getValue(); - - long lastActivityTime = activityState.getLastActivityTime(); - - // TODO: why this update is needed? if gateway session is present but this session is already gone we will not check if gateway's last activity time is greater than this one - SessionMetaData sessionMetaData = sessions.get(sessionId); - if (sessionMetaData != null) { - activityState = activityState.withSessionInfo(sessionMetaData.getSessionInfo()); - entry.setValue(activityState); - } else { - // log.info("Removing activity state! Session id: [{}]", sessionId); - activityStatesToRemove.add(sessionId); - } - // TODO: ask about this part end - - TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfo(); - - if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { - var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); - SessionMetaData gwSessionMetaData = sessions.get(gwSessionId); - if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { - ActivityState gwActivityState = sessionActivityStates.get(gwSessionId); - lastActivityTime = Math.max(gwActivityState.getLastActivityTime(), lastActivityTime); - } - } - - if (sessionMetaData != null && lastActivityTime < expirationTime) { - // log.info("Session has expired! Session id: [{}]", sessionId); - if (log.isDebugEnabled()) { - log.debug("[{}] Session has expired due to last activity time: {}!", sessionId, lastActivityTime); - } - sessions.remove(sessionId); - activityStatesToRemove.add(sessionId); - process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } else if (activityState.getLastReportedTime() < lastActivityTime) { - long finalLastActivityTime = lastActivityTime; - reportActivityStateToCore(sessionInfo, sessionMetaData, lastActivityTime, new TransportServiceCallback<>() { - @Override - public void onSuccess(Void msgAcknowledged) { - lock.readLock().lock(); - try { - updateLastReportedTime(sessionId, finalLastActivityTime); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public void onError(Throwable e) { - log.debug("[{}] Failed to report last activity time!", sessionId, e); - } - }); - } - entry.setValue(activityState.withReportedStatus(false)); - } - activityStatesToRemove.forEach(sessionActivityStates::remove); - } finally { - currentPeriodId.incrementAndGet(); - // log.info("Period change end! Current period id: [{}], activity states {}", currentPeriodId.get(), sessionActivityStates.keySet()); - // log.info("------------------------------------------------------------------------------------------------------------------------"); - lock.writeLock().unlock(); - } - } - - private void reportActivityStateToCore(TransportProtos.SessionInfoProto sessionInfo, long lastActivityTime, TransportServiceCallback msgAcknowledgedCallback) { - SessionMetaData sessionMetaData = sessions.get(toSessionId(sessionInfo)); - reportActivityStateToCore(sessionInfo, sessionMetaData, lastActivityTime, msgAcknowledgedCallback); - } - - private void reportActivityStateToCore( - TransportProtos.SessionInfoProto sessionInfo, SessionMetaData sessionMetaData, long lastActivityTime, TransportServiceCallback msgAcknowledgedCallback - ) { - log.info("Reporting activity state to core! Session id: [{}], last activity time: [{}], current period id: [{}]", - toSessionId(sessionInfo), lastActivityTime, currentPeriodId.get()); - TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) - .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) - .setLastActivityTime(lastActivityTime) - .build(); - process(sessionInfo, subscriptionInfo, msgAcknowledgedCallback); - } - - private void updateLastReportedTime(UUID sessionId, long lastActivityTime) { - sessionActivityStates.computeIfPresent(sessionId, (__, currentActivityState) -> { - long updatedLastReportedTime = Math.max(currentActivityState.getLastReportedTime(), lastActivityTime); - return currentActivityState.withLastReportedTime(updatedLastReportedTime); - }); - } - - } - @Override public void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityState.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityState.java new file mode 100644 index 0000000000..470699ec5d --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityState.java @@ -0,0 +1,44 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.service; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.gen.transport.TransportProtos; + +@Data +@EqualsAndHashCode(callSuper = true) +public class TransportActivityState extends ActivityState { + + private volatile TransportProtos.SessionInfoProto sessionInfoProto; + +} From 3461f6ae618a17529a8f0da39df16fac722716a1 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 7 Dec 2023 11:25:42 +0200 Subject: [PATCH 04/31] [WIP] Implemented three activity strategies for integration service --- ...irstAndLastIntegrationActivityManager.java | 196 ++++++++++++++ .../FirstOnlyIntegrationActivityManager.java | 192 ++++++++++++++ .../LastOnlyIntegrationActivityManager.java | 145 +++++++++++ ...StateManager.java => ActivityManager.java} | 4 +- .../activity/ActivityStateManagerImpl.java | 240 ------------------ .../activity/ActivityStateReportCallback.java | 2 - ...porter.java => ActivityStateReporter.java} | 6 +- .../service/DefaultTransportService.java | 46 ++-- .../LastOnlyTransportActivityManager.java | 131 ++++++++++ 9 files changed, 692 insertions(+), 270 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java create mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java create mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/{ActivityStateManager.java => ActivityManager.java} (92%) delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/{AsyncActivityStateReporter.java => ActivityStateReporter.java} (85%) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java new file mode 100644 index 0000000000..12be2101c4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java @@ -0,0 +1,196 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.service.integration.activity; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.data.util.Pair; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.transport.activity.ActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityStateReporter; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +@Slf4j +public class FirstAndLastIntegrationActivityManager implements ActivityManager { + + private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); + + @Data + private static class ActivityStateWrapper { + + volatile ActivityState activityState; + volatile boolean alreadyBeenReported; + + } + + private ScheduledExecutorService scheduler; + private final ActivityStateReporter reporter; + private final long reportingPeriodDurationMillis; + private final String name; + private boolean initialized; + + public FirstAndLastIntegrationActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { + this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); + this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation + this.name = name == null ? "activity-state-manager" : name; + } + + @Override + public synchronized void init() { + if (!initialized) { + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); + scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); + initialized = true; + } + } + + @Override + public void onActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + SettableFuture> reportCompletedFuture = SettableFuture.create(); + activityStates.compute(activityKey, (key, activityStateWrapper) -> { + if (activityStateWrapper == null) { + ActivityState activityState = newStateSupplier.get(); + activityState.setLastRecordedTime(newLastRecordedTime); + activityState.setLastReportedTime(0L); + activityStateWrapper = new ActivityStateWrapper(); + activityStateWrapper.setActivityState(activityState); + activityStateWrapper.setAlreadyBeenReported(false); + } else { + activityStateWrapper.getActivityState().setLastRecordedTime(newLastRecordedTime); + } + if (activityStateWrapper.isAlreadyBeenReported()) { + return activityStateWrapper; + } + var activityState = activityStateWrapper.getActivityState(); + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + reporter.report(key, activityState, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + reportCompletedFuture.set(Pair.of(key, reportedTime)); + } + + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + reportCompletedFuture.setException(t); + } + }); + } + activityStateWrapper.setAlreadyBeenReported(true); + return activityStateWrapper; + }); + Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { + @Override + public void onSuccess(Pair reportResult) { + updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); + } + + @Override + public void onFailure(@NonNull Throwable t) { + // TODO: log + } + }, MoreExecutors.directExecutor()); + } + + private void onReportingPeriodEnd() { + long expirationTime = System.currentTimeMillis() - reportingPeriodDurationMillis; + Set statesToRemove = new HashSet<>(); + Set> entries = activityStates.entrySet(); + for (Map.Entry entry : entries) { + var activityKey = entry.getKey(); + var activityStateWrapper = entry.getValue(); + var activityState = activityStateWrapper.getActivityState(); + if (activityState.getLastRecordedTime() < expirationTime) { + statesToRemove.add(activityKey); + } + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + reporter.report(activityKey, activityState, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + updateLastReportedTime(key, reportedTime); + } + + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + // TODO: log + } + }); + } + activityStateWrapper.setAlreadyBeenReported(false); + } + statesToRemove.forEach(activityStates::remove); + } + + private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { + activityStates.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getActivityState(); + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityStateWrapper; + }); + } + + @Override + public synchronized void destroy() { + if (initialized) { + initialized = false; + if (scheduler != null) { + scheduler.shutdown(); + try { + if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java new file mode 100644 index 0000000000..7a0bccd48c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java @@ -0,0 +1,192 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.service.integration.activity; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.data.util.Pair; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.transport.activity.ActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityStateReporter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +@Slf4j +public class FirstOnlyIntegrationActivityManager implements ActivityManager { + + private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); + + @Data + private static class ActivityStateWrapper { + + volatile ActivityState activityState; + volatile boolean alreadyBeenReported; + + } + + private ScheduledExecutorService scheduler; + private final ActivityStateReporter reporter; + private final long reportingPeriodDurationMillis; + private final String name; + private boolean initialized; + + public FirstOnlyIntegrationActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { + this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); + this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation + this.name = name == null ? "activity-state-manager" : name; + } + + @Override + public synchronized void init() { + if (!initialized) { + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); + scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); + initialized = true; + } + } + + @Override + public void onActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + SettableFuture> reportCompletedFuture = SettableFuture.create(); + activityStates.compute(activityKey, (key, activityStateWrapper) -> { + if (activityStateWrapper == null) { + ActivityState activityState = newStateSupplier.get(); + activityState.setLastRecordedTime(newLastRecordedTime); + activityState.setLastReportedTime(0L); + activityStateWrapper = new ActivityStateWrapper(); + activityStateWrapper.setActivityState(activityState); + activityStateWrapper.setAlreadyBeenReported(false); + } else { + activityStateWrapper.getActivityState().setLastRecordedTime(newLastRecordedTime); + } + if (activityStateWrapper.isAlreadyBeenReported()) { + return activityStateWrapper; + } + var activityState = activityStateWrapper.getActivityState(); + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + reporter.report(key, activityState, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + reportCompletedFuture.set(Pair.of(key, reportedTime)); + } + + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + reportCompletedFuture.setException(t); + } + }); + } + activityStateWrapper.setAlreadyBeenReported(true); + return activityStateWrapper; + }); + Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { + @Override + public void onSuccess(Pair reportResult) { + updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); + } + + @Override + public void onFailure(@NonNull Throwable t) { + // TODO: log + } + }, MoreExecutors.directExecutor()); + } + + private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { + activityStates.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getActivityState(); + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityStateWrapper; + }); + } + + private void onReportingPeriodEnd() { + Map statesToRemoveAndReport = new HashMap<>(); + Set> entries = activityStates.entrySet(); + for (Map.Entry entry : entries) { + var activityKey = entry.getKey(); + var activityStateWrapper = entry.getValue(); + var activityState = activityStateWrapper.getActivityState(); + if (!activityStateWrapper.isAlreadyBeenReported() && activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + statesToRemoveAndReport.put(activityKey, activityState); + } + activityStateWrapper.setAlreadyBeenReported(false); + } + statesToRemoveAndReport.forEach((key, state) -> reporter.report(key, state, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + activityStates.remove(key); + } + + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + // TODO: log + } + })); + } + + @Override + public synchronized void destroy() { + if (initialized) { + initialized = false; + if (scheduler != null) { + scheduler.shutdown(); + try { + if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java new file mode 100644 index 0000000000..0dd7aa6fc1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java @@ -0,0 +1,145 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.service.integration.activity; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.transport.activity.ActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityStateReporter; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +@Slf4j +public class LastOnlyIntegrationActivityManager implements ActivityManager { + + private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); + + private ScheduledExecutorService scheduler; + private final ActivityStateReporter reporter; + private final long reportingPeriodDurationMillis; + private final String name; + private boolean initialized; + + public LastOnlyIntegrationActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { + this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); + this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation + this.name = name == null ? "activity-state-manager" : name; + } + + @Override + public synchronized void init() { + if (!initialized) { + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); + scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); + initialized = true; + } + } + + @Override + public void onActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + activityStates.compute(activityKey, (key, activityState) -> { + if (activityState == null) { + activityState = newStateSupplier.get(); + activityState.setLastRecordedTime(newLastRecordedTime); + activityState.setLastReportedTime(0L); + } else { + activityState.setLastRecordedTime(newLastRecordedTime); + } + return activityState; + }); + } + + private void onReportingPeriodEnd() { + long expirationTime = System.currentTimeMillis() - reportingPeriodDurationMillis; + Set statesToRemove = new HashSet<>(); + Set> entries = activityStates.entrySet(); + for (Map.Entry entry : entries) { + var activityKey = entry.getKey(); + var activityState = entry.getValue(); + if (activityState.getLastRecordedTime() < expirationTime) { + statesToRemove.add(activityKey); + } + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + reporter.report(activityKey, activityState, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + updateLastReportedTime(key, reportedTime); + } + + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + // TODO: log + } + }); + } + } + statesToRemove.forEach(activityStates::remove); + } + + private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { + activityStates.computeIfPresent(key, (__, activityState) -> { + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityState; + }); + } + + @Override + public synchronized void destroy() { + if (initialized) { + initialized = false; + if (scheduler != null) { + scheduler.shutdown(); + try { + if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java similarity index 92% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManager.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index db24c2388d..d93756a922 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -32,11 +32,11 @@ package org.thingsboard.server.common.transport.activity; import java.util.function.Supplier; -public interface ActivityStateManager { +public interface ActivityManager { void init(); - void recordActivity(Key key, Supplier newActivityStateSupplier); + void onActivity(Key key, Supplier newStateSupplier); void destroy(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java deleted file mode 100644 index 7d87b02f19..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateManagerImpl.java +++ /dev/null @@ -1,240 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.common.transport.activity; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.springframework.data.util.Pair; -import org.thingsboard.common.util.ThingsBoardThreadFactory; - -import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -@Slf4j -public final class ActivityStateManagerImpl implements ActivityStateManager { - - private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); - - @Data - private static class ActivityStateWrapper { - - volatile ActivityState activityState; - volatile boolean alreadyBeenReported; - - } - - private ScheduledExecutorService scheduler; - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final AtomicInteger currentPeriodId = new AtomicInteger(); - private final AsyncActivityStateReporter reporter; - private final long reportingPeriodDurationMillis; - private final String name; - private boolean initialized; - - public ActivityStateManagerImpl(AsyncActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { - this.reporter = Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided reporter is null."); - this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation - this.name = name == null ? "activity-state-manager" : name; - } - - @Override - public synchronized void init() { - if (!initialized) { - initialized = true; - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); - scheduler.scheduleAtFixedRate(this::reportLastEventAndStartNewPeriod, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); - } - } - - @Override - public void recordActivity(Key activityKey, Supplier newActivityStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - int capturedPeriodId = currentPeriodId.get(); - lock.readLock().lock(); - SettableFuture> reportCompletedFuture = SettableFuture.create(); - try { - activityStates.compute(activityKey, (key, activityStateWrapper) -> { - if (activityStateWrapper == null) { - State activityState = newActivityStateSupplier.get(); - activityState.setLastRecordedTime(newLastRecordedTime); - activityState.setLastReportedTime(0L); - activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setActivityState(activityState); - activityStateWrapper.setAlreadyBeenReported(false); - } else { - activityStateWrapper.getActivityState().setLastRecordedTime(newLastRecordedTime); - } - if (activityStateWrapper.isAlreadyBeenReported()) { - return activityStateWrapper; - } - var activityState = activityStateWrapper.getActivityState(); - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.reportAsync(key, (State) activityState, new ActivityStateReportCallback<>() { - @Override - public void onSuccess(Key key, long reportedTime) { - reportCompletedFuture.set(Pair.of(key, reportedTime)); - } - - @Override - public void onFailure(Key key, Throwable t) { - reportCompletedFuture.setException(t); - } - - @Override - public void onRemove(Key key) { - lock.readLock().lock(); - try { - activityStates.remove(key); - } finally { - lock.readLock().unlock(); - } - } - }); - } - activityStateWrapper.setAlreadyBeenReported(true); - return activityStateWrapper; - }); - } finally { - lock.readLock().unlock(); - } - Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { - @Override - public void onSuccess(Pair reportResult) { - lock.readLock().lock(); - try { - updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public void onFailure(@NonNull Throwable t) { // TODO: add failure logging - lock.readLock().lock(); - try { - rollbackReportedStatus(activityKey, capturedPeriodId, newLastRecordedTime); - } finally { - lock.readLock().unlock(); - } - } - }, MoreExecutors.directExecutor()); - } - - private void rollbackReportedStatus(Key key, int capturedPeriodId, long newLastRecordedTime) { - activityStates.computeIfPresent(key, (__, activityStateWrapper) -> { - if (capturedPeriodId == currentPeriodId.get() && activityStateWrapper.getActivityState().getLastReportedTime() < newLastRecordedTime) { - activityStateWrapper.setAlreadyBeenReported(false); - } - return activityStateWrapper; - }); - } - - private void reportLastEventAndStartNewPeriod() { - lock.writeLock().lock(); - try { - Map activityStateMap = activityStates.entrySet().stream() - .peek(entry -> entry.getValue().setAlreadyBeenReported(false)) - .collect(Collectors.toMap(Map.Entry::getKey, entry -> (State) entry.getValue().getActivityState())); - reporter.reportAsync(activityStateMap, new ActivityStateReportCallback<>() { - @Override - public void onSuccess(Key key, long reportedTime) { - lock.readLock().lock(); - try { - updateLastReportedTime(key, reportedTime); - } finally { - lock.readLock().unlock(); - } - } - - @Override - public void onFailure(Key key, Throwable t) { - log.debug("Failed to report activity state for key [{}]!", key, t); - } - - @Override - public void onRemove(Key key) { - lock.readLock().lock(); - try { - activityStates.remove(key); - } finally { - lock.readLock().unlock(); - } - } - }); - } finally { - currentPeriodId.incrementAndGet(); - lock.writeLock().unlock(); - } - } - - private void updateLastReportedTime(Key key, long newLastReportedTime) { - activityStates.computeIfPresent(key, (__, activityStateWrapper) -> { - var activityState = activityStateWrapper.getActivityState(); - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityStateWrapper; - }); - } - - @Override - public synchronized void destroy() { - if (initialized) { - initialized = false; - if (scheduler != null) { - scheduler.shutdown(); - try { - if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { - scheduler.shutdownNow(); - } - } catch (InterruptedException e) { - scheduler.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - } - } - -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java index d9d897d4a1..4ff752d61d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java @@ -36,6 +36,4 @@ public interface ActivityStateReportCallback { void onFailure(Key key, Throwable t); - void onRemove(Key key); - } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AsyncActivityStateReporter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java similarity index 85% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AsyncActivityStateReporter.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java index ab9094c252..620646f894 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AsyncActivityStateReporter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java @@ -32,10 +32,10 @@ package org.thingsboard.server.common.transport.activity; import java.util.Map; -public interface AsyncActivityStateReporter { +public interface ActivityStateReporter { - void reportAsync(Key key, State activityState, ActivityStateReportCallback reportCallback); + void report(Key key, State activityState, ActivityStateReportCallback reportCallback); - void reportAsync(Map activityStates, ActivityStateReportCallback reportCallback); + void report(Map activityStates, ActivityStateReportCallback reportCallback); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 7769762b84..31ab2df09e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -72,16 +72,16 @@ import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportTenantProfileCache; -import org.thingsboard.server.common.transport.activity.ActivityStateManager; -import org.thingsboard.server.common.transport.activity.ActivityStateManagerImpl; +import org.thingsboard.server.common.transport.activity.ActivityManager; import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; -import org.thingsboard.server.common.transport.activity.AsyncActivityStateReporter; +import org.thingsboard.server.common.transport.activity.ActivityStateReporter; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.limits.EntityLimitKey; import org.thingsboard.server.common.transport.limits.EntityLimitsCache; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; +import org.thingsboard.server.common.transport.service.activity.LastOnlyTransportActivityManager; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -203,7 +203,7 @@ public class DefaultTransportService implements TransportService { private ExecutorService mainConsumerExecutor; public final ConcurrentMap sessions = new ConcurrentHashMap<>(); - private ActivityStateManager activityStateManager; + private ActivityManager activityManager; private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); private volatile boolean stopped = false; @@ -253,8 +253,8 @@ public class DefaultTransportService implements TransportService { transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); - activityStateManager = new ActivityStateManagerImpl<>(reporter, sessionReportTimeout, "transport-activity-state-manager"); - activityStateManager.init(); + activityManager = new LastOnlyTransportActivityManager(reporter, sessionReportTimeout, "transport-activity-state-manager"); + activityManager.init(); } // TODO: why sessions and session activities are managed in a separate maps? maybe it is better to manage in a single map @@ -267,44 +267,41 @@ public class DefaultTransportService implements TransportService { // this can cause lost activity updates if first event that got reported failed to enqueue in Kafka and next reporting period is already started // (maybe having a queue of "first" events will help with this, queue will be cleared once event was successfully queued or later time was reported by event in next period) // setting alreadyBeenReported status in callbacks ensures that events are reported but can cause several "first" events to be reported - private final AsyncActivityStateReporter reporter = new AsyncActivityStateReporter<>() { + private final ActivityStateReporter reporter = new ActivityStateReporter<>() { @Override - public void reportAsync(UUID sessionId, TransportActivityState activityState, ActivityStateReportCallback reportCallback) { + public void report(UUID sessionId, TransportActivityState activityState, ActivityStateReportCallback reportCallback) { SessionMetaData sessionMetaData = sessions.get(sessionId); - TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) - .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) - .setLastActivityTime(activityState.getLastRecordedTime()) - .build(); - process(activityState.getSessionInfoProto(), subscriptionInfo, new TransportServiceCallback<>() { + reportActivityStateToCore(activityState.getSessionInfoProto(), sessionMetaData, activityState.getLastRecordedTime(), new TransportServiceCallback<>() { @Override - public void onSuccess(Void msgAcknowledged) { + public void onSuccess(Void msg) { reportCallback.onSuccess(sessionId, activityState.getLastRecordedTime()); + } @Override public void onError(Throwable e) { reportCallback.onFailure(sessionId, e); + } }); } @Override - public void reportAsync(Map activityStates, ActivityStateReportCallback reportCallback) { + public void report(Map activityStates, ActivityStateReportCallback reportCallback) { long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; for (Map.Entry entry : activityStates.entrySet()) { var sessionId = entry.getKey(); var activityState = entry.getValue(); - long lastActivityTime = activityState.getLastRecordedTime(); - SessionMetaData sessionMetaData = sessions.get(sessionId); if (sessionMetaData != null) { activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); } else { - reportCallback.onRemove(sessionId); + log.info("Removing activity state due to session deregistration."); + activityStates.remove(sessionId); } + long lastActivityTime = activityState.getLastRecordedTime(); TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { @@ -323,7 +320,8 @@ public class DefaultTransportService implements TransportService { log.debug("[{}] Session has expired due to last activity time: {}!", sessionId, lastActivityTime); } sessions.remove(sessionId); - reportCallback.onRemove(sessionId); + log.info("Removing activity state due to session expiration."); + activityStates.remove(sessionId); process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); } else if (activityState.getLastReportedTime() < lastActivityTime) { @@ -406,8 +404,8 @@ public class DefaultTransportService implements TransportService { if (transportApiRequestTemplate != null) { transportApiRequestTemplate.stop(); } - if (activityStateManager != null) { - activityStateManager.destroy(); + if (activityManager != null) { + activityManager.destroy(); } } @@ -660,6 +658,7 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback callback) { + log.info("Received session event: [{}]", msg.getEvent()); if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -888,7 +887,7 @@ public class DefaultTransportService implements TransportService { } private void reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { - activityStateManager.recordActivity(toSessionId(sessionInfo), () -> { + activityManager.onActivity(toSessionId(sessionInfo), () -> { var activityState = new TransportActivityState(); activityState.setSessionInfoProto(sessionInfo); return activityState; @@ -950,6 +949,7 @@ public class DefaultTransportService implements TransportService { log.debug("Stopping scheduler to avoid resending response if request has been ack."); currentSession.getScheduledFuture().cancel(false); } + log.info("Deregistering session."); sessions.remove(toSessionId(sessionInfo)); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java new file mode 100644 index 0000000000..41f0207f72 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java @@ -0,0 +1,131 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.service.activity; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.transport.activity.ActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityStateReporter; +import org.thingsboard.server.common.transport.service.TransportActivityState; + +import java.util.Objects; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +@Slf4j +public class LastOnlyTransportActivityManager implements ActivityManager { + + private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); + + private ScheduledExecutorService scheduler; + private final ActivityStateReporter reporter; + private final long reportingPeriodDurationMillis; + private final String name; + private boolean initialized; + + public LastOnlyTransportActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { + this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); + this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation + this.name = name == null ? "activity-state-manager" : name; + } + + @Override + public synchronized void init() { + if (!initialized) { + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); + scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); + initialized = true; + } + } + + @Override + public void onActivity(UUID activityKey, Supplier newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + activityStates.compute(activityKey, (key, activityState) -> { + if (activityState == null) { + log.info("Creating new activity state."); + activityState = newStateSupplier.get(); + activityState.setLastRecordedTime(newLastRecordedTime); + activityState.setLastReportedTime(0L); + } else { + activityState.setLastRecordedTime(newLastRecordedTime); + } + return activityState; + }); + } + + private void onReportingPeriodEnd() { + reporter.report(activityStates, new ActivityStateReportCallback<>() { + @Override + public void onSuccess(UUID key, long reportedTime) { + updateLastReportedTime(key, reportedTime); + } + + @Override + public void onFailure(UUID uuid, Throwable t) { + // TODO: log + } + }); + } + + private void updateLastReportedTime(UUID key, long newLastReportedTime) { + activityStates.computeIfPresent(key, (__, activityState) -> { + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityState; + }); + } + + @Override + public synchronized void destroy() { + if (initialized) { + initialized = false; + if (scheduler != null) { + scheduler.shutdown(); + try { + if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + } + +} From 2ca1f719981be755d01b1956bcfdefe8f3475e74 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 7 Dec 2023 17:09:51 +0200 Subject: [PATCH 05/31] [WIP] Migrate to using spring beans, add properties to yaml --- ...irstAndLastIntegrationActivityManager.java | 99 +++------ .../FirstOnlyIntegrationActivityManager.java | 110 +++------- .../LastOnlyIntegrationActivityManager.java | 82 ++----- .../src/main/resources/thingsboard.yml | 9 +- .../common/transport/TransportService.java | 3 + .../activity/AbstractActivityManager.java | 114 ++++++++++ .../transport/activity/ActivityManager.java | 6 + ...lback.java => ActivityReportCallback.java} | 2 +- .../activity/ActivityStateReporter.java | 7 +- .../service/DefaultTransportService.java | 129 +++-------- .../FirstAndLastTransportActivityManager.java | 203 +++++++++++++++++ .../FirstOnlyTransportActivityManager.java | 204 ++++++++++++++++++ .../LastOnlyTransportActivityManager.java | 143 ++++++------ 13 files changed, 731 insertions(+), 380 deletions(-) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/{ActivityStateReportCallback.java => ActivityReportCallback.java} (97%) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java index 12be2101c4..4a38af1a4d 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java @@ -37,80 +37,54 @@ import com.google.common.util.concurrent.SettableFuture; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.util.Pair; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.transport.activity.ActivityManager; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityStateReporter; -import java.util.HashSet; import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @Slf4j -public class FirstAndLastIntegrationActivityManager implements ActivityManager { +@Component +@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first-and-last") +public class FirstAndLastIntegrationActivityManager extends AbstractActivityManager { - private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); + private final ConcurrentMap states = new ConcurrentHashMap<>(); @Data private static class ActivityStateWrapper { - volatile ActivityState activityState; + volatile ActivityState state; volatile boolean alreadyBeenReported; } - private ScheduledExecutorService scheduler; - private final ActivityStateReporter reporter; - private final long reportingPeriodDurationMillis; - private final String name; - private boolean initialized; - - public FirstAndLastIntegrationActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { - this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); - this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation - this.name = name == null ? "activity-state-manager" : name; - } - - @Override - public synchronized void init() { - if (!initialized) { - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); - scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); - initialized = true; - } - } - @Override - public void onActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { + protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { long newLastRecordedTime = System.currentTimeMillis(); SettableFuture> reportCompletedFuture = SettableFuture.create(); - activityStates.compute(activityKey, (key, activityStateWrapper) -> { + states.compute(activityKey, (key, activityStateWrapper) -> { if (activityStateWrapper == null) { ActivityState activityState = newStateSupplier.get(); activityState.setLastRecordedTime(newLastRecordedTime); activityState.setLastReportedTime(0L); activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setActivityState(activityState); + activityStateWrapper.setState(activityState); activityStateWrapper.setAlreadyBeenReported(false); } else { - activityStateWrapper.getActivityState().setLastRecordedTime(newLastRecordedTime); + activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); } if (activityStateWrapper.isAlreadyBeenReported()) { return activityStateWrapper; } - var activityState = activityStateWrapper.getActivityState(); + var activityState = activityStateWrapper.getState(); if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.report(key, activityState, new ActivityStateReportCallback<>() { + reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { reportCompletedFuture.set(Pair.of(key, reportedTime)); @@ -133,24 +107,23 @@ public class FirstAndLastIntegrationActivityManager implements ActivityManager statesToRemove = new HashSet<>(); - Set> entries = activityStates.entrySet(); - for (Map.Entry entry : entries) { + @Override + protected void onReportingPeriodEnd() { + long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; + for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityStateWrapper = entry.getValue(); - var activityState = activityStateWrapper.getActivityState(); + var activityState = activityStateWrapper.getState(); if (activityState.getLastRecordedTime() < expirationTime) { - statesToRemove.add(activityKey); + states.remove(activityKey); } if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.report(activityKey, activityState, new ActivityStateReportCallback<>() { + reporter.report(activityKey, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { updateLastReportedTime(key, reportedTime); @@ -158,39 +131,21 @@ public class FirstAndLastIntegrationActivityManager implements ActivityManager { - var activityState = activityStateWrapper.getActivityState(); + states.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getState(); activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); return activityStateWrapper; }); } - @Override - public synchronized void destroy() { - if (initialized) { - initialized = false; - if (scheduler != null) { - scheduler.shutdown(); - try { - if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { - scheduler.shutdownNow(); - } - } catch (InterruptedException e) { - scheduler.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - } - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java index 7a0bccd48c..b9965098df 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java @@ -37,80 +37,54 @@ import com.google.common.util.concurrent.SettableFuture; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.util.Pair; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.transport.activity.ActivityManager; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityStateReporter; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @Slf4j -public class FirstOnlyIntegrationActivityManager implements ActivityManager { +@Component +@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first") +public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager { - private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); + private final ConcurrentMap states = new ConcurrentHashMap<>(); @Data private static class ActivityStateWrapper { - volatile ActivityState activityState; + volatile ActivityState state; volatile boolean alreadyBeenReported; } - private ScheduledExecutorService scheduler; - private final ActivityStateReporter reporter; - private final long reportingPeriodDurationMillis; - private final String name; - private boolean initialized; - - public FirstOnlyIntegrationActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { - this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); - this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation - this.name = name == null ? "activity-state-manager" : name; - } - - @Override - public synchronized void init() { - if (!initialized) { - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); - scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); - initialized = true; - } - } - @Override - public void onActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { + protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { long newLastRecordedTime = System.currentTimeMillis(); SettableFuture> reportCompletedFuture = SettableFuture.create(); - activityStates.compute(activityKey, (key, activityStateWrapper) -> { + states.compute(activityKey, (key, activityStateWrapper) -> { if (activityStateWrapper == null) { ActivityState activityState = newStateSupplier.get(); activityState.setLastRecordedTime(newLastRecordedTime); activityState.setLastReportedTime(0L); activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setActivityState(activityState); + activityStateWrapper.setState(activityState); activityStateWrapper.setAlreadyBeenReported(false); } else { - activityStateWrapper.getActivityState().setLastRecordedTime(newLastRecordedTime); + activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); } if (activityStateWrapper.isAlreadyBeenReported()) { return activityStateWrapper; } - var activityState = activityStateWrapper.getActivityState(); + var activityState = activityStateWrapper.getState(); if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.report(key, activityState, new ActivityStateReportCallback<>() { + reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { reportCompletedFuture.set(Pair.of(key, reportedTime)); @@ -133,59 +107,39 @@ public class FirstOnlyIntegrationActivityManager implements ActivityManager { - var activityState = activityStateWrapper.getActivityState(); + states.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getState(); activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); return activityStateWrapper; }); } - private void onReportingPeriodEnd() { - Map statesToRemoveAndReport = new HashMap<>(); - Set> entries = activityStates.entrySet(); - for (Map.Entry entry : entries) { + @Override + protected void onReportingPeriodEnd() { + for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityStateWrapper = entry.getValue(); - var activityState = activityStateWrapper.getActivityState(); + var activityState = activityStateWrapper.getState(); if (!activityStateWrapper.isAlreadyBeenReported() && activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - statesToRemoveAndReport.put(activityKey, activityState); - } - activityStateWrapper.setAlreadyBeenReported(false); - } - statesToRemoveAndReport.forEach((key, state) -> reporter.report(key, state, new ActivityStateReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - activityStates.remove(key); - } - - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - // TODO: log - } - })); - } + reporter.report(activityKey, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + states.remove(key); + } - @Override - public synchronized void destroy() { - if (initialized) { - initialized = false; - if (scheduler != null) { - scheduler.shutdown(); - try { - if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { - scheduler.shutdownNow(); + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + log.debug("[{}] Failed to report last activity event in a period for device with id: [{}]", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); } - } catch (InterruptedException e) { - scheduler.shutdownNow(); - Thread.currentThread().interrupt(); - } + }); } + activityStateWrapper.setAlreadyBeenReported(false); } } diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java index 0dd7aa6fc1..bffdc49054 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java @@ -31,54 +31,28 @@ package org.thingsboard.server.service.integration.activity; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.transport.activity.ActivityManager; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityStateReporter; -import java.util.HashSet; import java.util.Map; -import java.util.Objects; -import java.util.Random; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @Slf4j -public class LastOnlyIntegrationActivityManager implements ActivityManager { +@Component +@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "last") +public class LastOnlyIntegrationActivityManager extends AbstractActivityManager { - private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); - - private ScheduledExecutorService scheduler; - private final ActivityStateReporter reporter; - private final long reportingPeriodDurationMillis; - private final String name; - private boolean initialized; - - public LastOnlyIntegrationActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { - this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); - this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation - this.name = name == null ? "activity-state-manager" : name; - } - - @Override - public synchronized void init() { - if (!initialized) { - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); - scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); - initialized = true; - } - } + private final ConcurrentMap states = new ConcurrentHashMap<>(); @Override - public void onActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { + protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { long newLastRecordedTime = System.currentTimeMillis(); - activityStates.compute(activityKey, (key, activityState) -> { + states.compute(activityKey, (__, activityState) -> { if (activityState == null) { activityState = newStateSupplier.get(); activityState.setLastRecordedTime(newLastRecordedTime); @@ -90,18 +64,17 @@ public class LastOnlyIntegrationActivityManager implements ActivityManager statesToRemove = new HashSet<>(); - Set> entries = activityStates.entrySet(); - for (Map.Entry entry : entries) { + @Override + public void onReportingPeriodEnd() { + long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; + for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityState = entry.getValue(); if (activityState.getLastRecordedTime() < expirationTime) { - statesToRemove.add(activityKey); + states.remove(activityKey); } if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.report(activityKey, activityState, new ActivityStateReportCallback<>() { + reporter.report(activityKey, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { updateLastReportedTime(key, reportedTime); @@ -109,37 +82,18 @@ public class LastOnlyIntegrationActivityManager implements ActivityManager { + states.computeIfPresent(key, (__, activityState) -> { activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); return activityState; }); } - @Override - public synchronized void destroy() { - if (initialized) { - initialized = false; - if (scheduler != null) { - scheduler.shutdown(); - try { - if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { - scheduler.shutdownNow(); - } - } catch (InterruptedException e) { - scheduler.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - } - } - } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e746dc9dba..73f0622cd1 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -866,6 +866,13 @@ transport: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" # Interval of periodic check for expired sessions and report of the changes to session last activity time report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" + activity: + # This property specifies the strategy for reporting activity events within each reporting period. + # The accepted values are 'first', 'last', and 'first and last'. + # - 'first': Only the first activity event in each reporting period is reported. + # - 'last': Only the last activity event in the reporting period is reported. + # - 'first-and-last': Both the first and last activity events in the reporting period are reported. + reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:first-and-last}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" @@ -1695,4 +1702,4 @@ management: web: exposure: # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics). - include: '${METRICS_ENDPOINTS_EXPOSE:info}' \ No newline at end of file + include: '${METRICS_ENDPOINTS_EXPOSE:info}' 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 index f51b399a5b..067527efec 100644 --- 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 @@ -63,6 +63,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce import org.thingsboard.server.gen.transport.TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg; import java.util.List; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; @@ -161,5 +162,7 @@ public interface TransportService { boolean hasSession(SessionInfoProto sessionInfo); + SessionMetaData getSession(UUID sessionId); + void createGaugeStats(String openConnections, AtomicInteger connectionsCounter); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java new file mode 100644 index 0000000000..0c7e00816a --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -0,0 +1,114 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity; + +import org.thingsboard.common.util.ThingsBoardThreadFactory; + +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +public abstract class AbstractActivityManager implements ActivityManager { + + private String name; + protected long reportingPeriodMillis; + protected ActivityStateReporter reporter; + private ScheduledExecutorService scheduler; + protected boolean initialized; + + @Override + public synchronized void init() { + Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided activity reporter is null."); + if (reportingPeriodMillis < 1000) { + throw new IllegalArgumentException("Failed to initialize activity manager: provided reporting period duration is less that 1 second."); + } + if (!initialized) { + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name == null ? "activity-manager" : name)); + scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodMillis), reportingPeriodMillis, TimeUnit.MILLISECONDS); + initialized = true; + } + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public void setReportingPeriod(long reportingPeriodMillis) { + this.reportingPeriodMillis = reportingPeriodMillis; + } + + @Override + public void setActivityReporter(ActivityStateReporter activityReporter) { + reporter = activityReporter; + } + + @Override + public void onActivity(Key key, Supplier newStateSupplier) { + if (!initialized) { + throw new IllegalStateException(name + " is not initialized."); + } + if (key == null) { + throw new IllegalArgumentException("Activity key can't be null."); + } + if (newStateSupplier == null) { + throw new IllegalArgumentException("New activity state supplier can't be null."); + } + doOnActivity(key, newStateSupplier); + } + + protected abstract void doOnActivity(Key key, Supplier newStateSupplier); + + protected abstract void onReportingPeriodEnd(); + + @Override + public synchronized void destroy() { + if (initialized) { + initialized = false; + if (scheduler != null) { + scheduler.shutdown(); + try { + if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index d93756a922..ba09ccf773 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -34,6 +34,12 @@ import java.util.function.Supplier; public interface ActivityManager { + void setName(String name); + + void setReportingPeriod(long reportingPeriodMillis); + + void setActivityReporter(ActivityStateReporter activityReporter); + void init(); void onActivity(Key key, Supplier newStateSupplier); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java similarity index 97% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java index 4ff752d61d..9d009d2707 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReportCallback.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java @@ -30,7 +30,7 @@ */ package org.thingsboard.server.common.transport.activity; -public interface ActivityStateReportCallback { +public interface ActivityReportCallback { void onSuccess(Key key, long reportedTime); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java index 620646f894..1092c569d2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java @@ -30,12 +30,9 @@ */ package org.thingsboard.server.common.transport.activity; -import java.util.Map; - +@FunctionalInterface public interface ActivityStateReporter { - void report(Key key, State activityState, ActivityStateReportCallback reportCallback); - - void report(Map activityStates, ActivityStateReportCallback reportCallback); + void report(Key key, long timeToReport, State activityState, ActivityReportCallback callback); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 31ab2df09e..6453cbd24c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -73,7 +73,6 @@ import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.activity.ActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; import org.thingsboard.server.common.transport.activity.ActivityStateReporter; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; @@ -81,7 +80,6 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.limits.EntityLimitKey; import org.thingsboard.server.common.transport.limits.EntityLimitsCache; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; -import org.thingsboard.server.common.transport.service.activity.LastOnlyTransportActivityManager; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -187,8 +185,8 @@ public class DefaultTransportService implements TransportService { private final ApplicationEventPublisher eventPublisher; private final TransportResourceCache transportResourceCache; private final NotificationRuleProcessor notificationRuleProcessor; - private final EntityLimitsCache entityLimitsCache; + private ActivityManager activityManager; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -203,7 +201,6 @@ public class DefaultTransportService implements TransportService { private ExecutorService mainConsumerExecutor; public final ConcurrentMap sessions = new ConcurrentHashMap<>(); - private ActivityManager activityManager; private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); private volatile boolean stopped = false; @@ -237,6 +234,11 @@ public class DefaultTransportService implements TransportService { this.entityLimitsCache = entityLimitsCache; } + @Autowired + public void setActivityManager(ActivityManager activityManager) { + this.activityManager = activityManager; + } + @PostConstruct public void init() { this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer"); @@ -253,104 +255,32 @@ public class DefaultTransportService implements TransportService { transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); - activityManager = new LastOnlyTransportActivityManager(reporter, sessionReportTimeout, "transport-activity-state-manager"); + + activityManager.setName("transport-activity-manager"); + activityManager.setReportingPeriod(sessionReportTimeout); + activityManager.setActivityReporter(activityReporter); activityManager.init(); } - // TODO: why sessions and session activities are managed in a separate maps? maybe it is better to manage in a single map - // (eg. we can check if we had sent last activity event when deregistering session) - - // TODO: currently if activity event is received between precise session expiration time and reportLastEventAndStartNewPeriod() call - // we will "resurrect" this session meaning that it will be considered alive but in fact it did not perform any activity for more than timeout allows - - // TODO: I optimistically set alreadyBeenReported status to true to avoid reporting several activity events if they arrive in rapid succession - // this can cause lost activity updates if first event that got reported failed to enqueue in Kafka and next reporting period is already started - // (maybe having a queue of "first" events will help with this, queue will be cleared once event was successfully queued or later time was reported by event in next period) - // setting alreadyBeenReported status in callbacks ensures that events are reported but can cause several "first" events to be reported - private final ActivityStateReporter reporter = new ActivityStateReporter<>() { - @Override - public void report(UUID sessionId, TransportActivityState activityState, ActivityStateReportCallback reportCallback) { - SessionMetaData sessionMetaData = sessions.get(sessionId); - reportActivityStateToCore(activityState.getSessionInfoProto(), sessionMetaData, activityState.getLastRecordedTime(), new TransportServiceCallback<>() { - @Override - public void onSuccess(Void msg) { - reportCallback.onSuccess(sessionId, activityState.getLastRecordedTime()); - - } - - @Override - public void onError(Throwable e) { - reportCallback.onFailure(sessionId, e); - - } - }); - } - - @Override - public void report(Map activityStates, ActivityStateReportCallback reportCallback) { - long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; - for (Map.Entry entry : activityStates.entrySet()) { - var sessionId = entry.getKey(); - var activityState = entry.getValue(); - - SessionMetaData sessionMetaData = sessions.get(sessionId); - if (sessionMetaData != null) { - activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); - } else { - log.info("Removing activity state due to session deregistration."); - activityStates.remove(sessionId); - } - - long lastActivityTime = activityState.getLastRecordedTime(); - TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); - - if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { - var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); - SessionMetaData gwSessionMetaData = sessions.get(gwSessionId); - if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { - TransportActivityState gwActivityState = activityStates.get(gwSessionId); - if (gwActivityState != null) { - lastActivityTime = Math.max(gwActivityState.getLastRecordedTime(), lastActivityTime); - } - } - } - - if (sessionMetaData != null && lastActivityTime < expirationTime) { - if (log.isDebugEnabled()) { - log.debug("[{}] Session has expired due to last activity time: {}!", sessionId, lastActivityTime); - } - sessions.remove(sessionId); - log.info("Removing activity state due to session expiration."); - activityStates.remove(sessionId); - process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } else if (activityState.getLastReportedTime() < lastActivityTime) { - long finalLastActivityTime = lastActivityTime; - reportActivityStateToCore(sessionInfo, sessionMetaData, lastActivityTime, new TransportServiceCallback<>() { - @Override - public void onSuccess(Void msgAcknowledged) { - reportCallback.onSuccess(sessionId, finalLastActivityTime); - } + private final ActivityStateReporter activityReporter = (sessionId, timeToReport, state, reportCallback) -> { + SessionMetaData sessionMetaData = sessions.get(sessionId); + TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) + .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) + .setLastActivityTime(timeToReport) + .build(); + process(state.getSessionInfoProto(), subscriptionInfo, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + reportCallback.onSuccess(sessionId, timeToReport); - @Override - public void onError(Throwable e) { - reportCallback.onFailure(sessionId, e); - } - }); - } } - } - private void reportActivityStateToCore( - TransportProtos.SessionInfoProto sessionInfo, SessionMetaData sessionMetaData, long lastActivityTime, TransportServiceCallback callback - ) { - TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) - .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) - .setLastActivityTime(lastActivityTime) - .build(); - process(sessionInfo, subscriptionInfo, callback); - } + @Override + public void onError(Throwable e) { + reportCallback.onFailure(sessionId, e); + } + }); }; @AfterStartUp(order = AfterStartUp.TRANSPORT_SERVICE) @@ -658,7 +588,6 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback callback) { - log.info("Received session event: [{}]", msg.getEvent()); if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -949,7 +878,6 @@ public class DefaultTransportService implements TransportService { log.debug("Stopping scheduler to avoid resending response if request has been ack."); currentSession.getScheduledFuture().cancel(false); } - log.info("Deregistering session."); sessions.remove(toSessionId(sessionInfo)); } @@ -1370,6 +1298,11 @@ public class DefaultTransportService implements TransportService { return sessions.containsKey(toSessionId(sessionInfo)); } + @Override + public SessionMetaData getSession(UUID sessionId) { + return sessions.get(sessionId); + } + @Override public void createGaugeStats(String statsName, AtomicInteger number) { statsFactory.createGauge(StatsType.TRANSPORT + "." + statsName, number); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java new file mode 100644 index 0000000000..e6bffa4dfd --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java @@ -0,0 +1,203 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.service.activity; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; +import org.thingsboard.server.common.transport.service.SessionMetaData; +import org.thingsboard.server.common.transport.service.TransportActivityState; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; + +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EXPIRED_NOTIFICATION_PROTO; + +@Slf4j +@Component +@ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first-and-last") +public class FirstAndLastTransportActivityManager extends AbstractActivityManager { + + private final ConcurrentMap states = new ConcurrentHashMap<>(); + + @Data + private static class ActivityStateWrapper { + + volatile TransportActivityState state; + volatile boolean alreadyBeenReported; + + } + + @Value("${transport.sessions.inactivity_timeout}") + private long sessionInactivityTimeout; + + @Autowired + private TransportService transportService; + + @Override + protected void doOnActivity(UUID sessionId, Supplier newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + SettableFuture> reportCompletedFuture = SettableFuture.create(); + states.compute(sessionId, (key, activityStateWrapper) -> { + if (activityStateWrapper == null) { + TransportActivityState activityState = newStateSupplier.get(); + activityState.setLastRecordedTime(newLastRecordedTime); + activityState.setLastReportedTime(0L); + activityStateWrapper = new ActivityStateWrapper(); + activityStateWrapper.setState(activityState); + activityStateWrapper.setAlreadyBeenReported(false); + } else { + activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); + } + if (activityStateWrapper.isAlreadyBeenReported()) { + return activityStateWrapper; + } + var activityState = activityStateWrapper.getState(); + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(UUID key, long reportedTime) { + reportCompletedFuture.set(Pair.of(key, reportedTime)); + } + + @Override + public void onFailure(UUID key, Throwable t) { + reportCompletedFuture.setException(t); + } + }); + } + activityStateWrapper.setAlreadyBeenReported(true); + return activityStateWrapper; + }); + Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { + @Override + public void onSuccess(Pair reportResult) { + updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); + } + + @Override + public void onFailure(@NonNull Throwable t) { + log.debug("[{}] Failed to report first activity event in a period for session.", sessionId); + } + }, MoreExecutors.directExecutor()); + } + + @Override + protected void onReportingPeriodEnd() { + long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; + for (Map.Entry entry : states.entrySet()) { + var sessionId = entry.getKey(); + var activityStateWrapper = entry.getValue(); + var activityState = activityStateWrapper.getState(); + + SessionMetaData sessionMetaData = transportService.getSession(sessionId); + if (sessionMetaData != null) { + activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); + } else { + states.remove(sessionId); + } + + long lastActivityTime = activityState.getLastRecordedTime(); + TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); + + if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { + var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); + SessionMetaData gwSessionMetaData = transportService.getSession(gwSessionId); + if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { + ActivityStateWrapper gwActivityStateWrapper = states.get(gwSessionId); + if (gwActivityStateWrapper != null) { + lastActivityTime = Math.max(gwActivityStateWrapper.getState().getLastRecordedTime(), lastActivityTime); + } + } + } + + boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; + if (hasExpired) { + if (log.isDebugEnabled()) { + log.debug("[{}] Session has expired due to last activity time: [{}]!", sessionId, lastActivityTime); + } + transportService.deregisterSession(sessionInfo); + transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + states.remove(sessionId); + } + + @Override + public void onError(Throwable e) { + states.remove(sessionId); + } + }); + sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); + } else if (activityState.getLastReportedTime() < lastActivityTime) { + reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(UUID key, long reportedTime) { + updateLastReportedTime(key, reportedTime); + } + + @Override + public void onFailure(UUID key, Throwable t) { + log.debug("[{}] Failed to report last activity event in a period for session.", sessionId); + } + }); + } + activityStateWrapper.setAlreadyBeenReported(false); + } + } + + private void updateLastReportedTime(UUID key, long newLastReportedTime) { + states.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getState(); + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityStateWrapper; + }); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java new file mode 100644 index 0000000000..8cefbb0b46 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java @@ -0,0 +1,204 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.service.activity; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; +import org.thingsboard.server.common.transport.service.SessionMetaData; +import org.thingsboard.server.common.transport.service.TransportActivityState; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; + +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EXPIRED_NOTIFICATION_PROTO; + +@Slf4j +@Component +@ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first") +public class FirstOnlyTransportActivityManager extends AbstractActivityManager { + + private final ConcurrentMap states = new ConcurrentHashMap<>(); + + @Data + private static class ActivityStateWrapper { + + volatile TransportActivityState state; + volatile boolean alreadyBeenReported; + + } + + @Value("${transport.sessions.inactivity_timeout}") + private long sessionInactivityTimeout; + + @Autowired + private TransportService transportService; + + @Override + protected void doOnActivity(UUID sessionId, Supplier newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + SettableFuture> reportCompletedFuture = SettableFuture.create(); + states.compute(sessionId, (key, activityStateWrapper) -> { + if (activityStateWrapper == null) { + TransportActivityState activityState = newStateSupplier.get(); + activityState.setLastRecordedTime(newLastRecordedTime); + activityState.setLastReportedTime(0L); + activityStateWrapper = new ActivityStateWrapper(); + activityStateWrapper.setState(activityState); + activityStateWrapper.setAlreadyBeenReported(false); + } else { + activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); + } + if (activityStateWrapper.isAlreadyBeenReported()) { + return activityStateWrapper; + } + var activityState = activityStateWrapper.getState(); + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(UUID key, long reportedTime) { + reportCompletedFuture.set(Pair.of(key, reportedTime)); + } + + @Override + public void onFailure(UUID key, Throwable t) { + reportCompletedFuture.setException(t); + } + }); + } + activityStateWrapper.setAlreadyBeenReported(true); + return activityStateWrapper; + }); + Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { + @Override + public void onSuccess(Pair reportResult) { + updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); + } + + @Override + public void onFailure(@NonNull Throwable t) { + log.debug("[{}] Failed to report first activity event in a period for session.", sessionId); + } + }, MoreExecutors.directExecutor()); + } + + @Override + protected void onReportingPeriodEnd() { + long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; + for (Map.Entry entry : states.entrySet()) { + var sessionId = entry.getKey(); + var activityStateWrapper = entry.getValue(); + var activityState = activityStateWrapper.getState(); + + SessionMetaData sessionMetaData = transportService.getSession(sessionId); + if (sessionMetaData != null) { + activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); + } else { + states.remove(sessionId); + } + + long lastActivityTime = activityState.getLastRecordedTime(); + TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); + + if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { + var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); + SessionMetaData gwSessionMetaData = transportService.getSession(gwSessionId); + if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { + ActivityStateWrapper gwActivityStateWrapper = states.get(gwSessionId); + if (gwActivityStateWrapper != null) { + lastActivityTime = Math.max(gwActivityStateWrapper.getState().getLastRecordedTime(), lastActivityTime); + } + } + } + + boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; + if (hasExpired) { + if (log.isDebugEnabled()) { + log.debug("[{}] Session has expired due to last activity time: {}!", sessionId, lastActivityTime); + } + transportService.deregisterSession(sessionInfo); + transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + states.remove(sessionId); + } + + @Override + public void onError(Throwable e) { + states.remove(sessionId); + } + }); + sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); + } + if ((sessionMetaData == null || hasExpired) && activityState.getLastReportedTime() < lastActivityTime) { + reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(UUID key, long reportedTime) { + updateLastReportedTime(key, reportedTime); + } + + @Override + public void onFailure(UUID key, Throwable t) { + log.debug("[{}] Failed to report last activity event in a period for session.", sessionId); + } + }); + } + activityStateWrapper.setAlreadyBeenReported(false); + } + } + + private void updateLastReportedTime(UUID key, long newLastReportedTime) { + states.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getState(); + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityStateWrapper; + }); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java index 41f0207f72..bc723e0e4a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java @@ -31,54 +31,45 @@ package org.thingsboard.server.common.transport.service.activity; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.transport.activity.ActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityStateReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityStateReporter; +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.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; +import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; +import org.thingsboard.server.gen.transport.TransportProtos; -import java.util.Objects; -import java.util.Random; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -@Slf4j -public class LastOnlyTransportActivityManager implements ActivityManager { +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; +import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EXPIRED_NOTIFICATION_PROTO; - private final ConcurrentMap activityStates = new ConcurrentHashMap<>(); +@Slf4j +@Component +@ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "last") +public class LastOnlyTransportActivityManager extends AbstractActivityManager { - private ScheduledExecutorService scheduler; - private final ActivityStateReporter reporter; - private final long reportingPeriodDurationMillis; - private final String name; - private boolean initialized; + private final ConcurrentMap states = new ConcurrentHashMap<>(); - public LastOnlyTransportActivityManager(ActivityStateReporter reporter, long reportingPeriodDurationMillis, String name) { - this.reporter = Objects.requireNonNull(reporter, "Failed to create activity manager: provided reporter is null."); - this.reportingPeriodDurationMillis = reportingPeriodDurationMillis; // TODO: add min/max duration validation - this.name = name == null ? "activity-state-manager" : name; - } + @Value("${transport.sessions.inactivity_timeout}") + private long sessionInactivityTimeout; - @Override - public synchronized void init() { - if (!initialized) { - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name)); - scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodDurationMillis), reportingPeriodDurationMillis, TimeUnit.MILLISECONDS); - initialized = true; - } - } + @Autowired + private TransportService transportService; @Override - public void onActivity(UUID activityKey, Supplier newStateSupplier) { + protected void doOnActivity(UUID activityKey, Supplier newStateSupplier) { long newLastRecordedTime = System.currentTimeMillis(); - activityStates.compute(activityKey, (key, activityState) -> { + states.compute(activityKey, (__, activityState) -> { if (activityState == null) { - log.info("Creating new activity state."); activityState = newStateSupplier.get(); activityState.setLastRecordedTime(newLastRecordedTime); activityState.setLastReportedTime(0L); @@ -89,43 +80,73 @@ public class LastOnlyTransportActivityManager implements ActivityManager() { - @Override - public void onSuccess(UUID key, long reportedTime) { - updateLastReportedTime(key, reportedTime); + @Override + protected void onReportingPeriodEnd() { + long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; + for (Map.Entry entry : states.entrySet()) { + var sessionId = entry.getKey(); + var activityState = entry.getValue(); + + SessionMetaData sessionMetaData = transportService.getSession(sessionId); + if (sessionMetaData != null) { + activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); + } else { + states.remove(sessionId); } - @Override - public void onFailure(UUID uuid, Throwable t) { - // TODO: log + long lastActivityTime = activityState.getLastRecordedTime(); + TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); + + if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { + var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); + SessionMetaData gwSessionMetaData = transportService.getSession(gwSessionId); + if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { + TransportActivityState gwActivityState = states.get(gwSessionId); + if (gwActivityState != null) { + lastActivityTime = Math.max(gwActivityState.getLastRecordedTime(), lastActivityTime); + } + } } - }); + + boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; + if (hasExpired) { + if (log.isDebugEnabled()) { + log.debug("[{}] Session has expired due to last activity time: [{}]!", sessionId, lastActivityTime); + } + transportService.deregisterSession(sessionInfo); + transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + states.remove(sessionId); + } + + @Override + public void onError(Throwable e) { + states.remove(sessionId); + } + }); + sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); + } else if (activityState.getLastReportedTime() < lastActivityTime) { + reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(UUID key, long reportedTime) { + updateLastReportedTime(key, reportedTime); + } + + @Override + public void onFailure(UUID key, Throwable t) { + log.debug("[{}] Failed to report last activity event in a period for session.", sessionId); + } + }); + } + } } private void updateLastReportedTime(UUID key, long newLastReportedTime) { - activityStates.computeIfPresent(key, (__, activityState) -> { + states.computeIfPresent(key, (__, activityState) -> { activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); return activityState; }); } - @Override - public synchronized void destroy() { - if (initialized) { - initialized = false; - if (scheduler != null) { - scheduler.shutdown(); - try { - if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { - scheduler.shutdownNow(); - } - } catch (InterruptedException e) { - scheduler.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - } - } - } From c3556eba7d5c2560bde18944f57ab7b2bb14e8cf Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 8 Dec 2023 09:55:37 +0200 Subject: [PATCH 06/31] [WIP] Refactor abstract and integration activity managers --- ...irstAndLastIntegrationActivityManager.java | 31 +++++----- .../FirstOnlyIntegrationActivityManager.java | 59 ++++++++++--------- .../LastOnlyIntegrationActivityManager.java | 15 ++--- .../activity/AbstractActivityManager.java | 42 ++++++------- .../transport/activity/ActivityManager.java | 8 +-- 5 files changed, 72 insertions(+), 83 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java index 4a38af1a4d..e0dcc97ac0 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java @@ -70,19 +70,16 @@ public class FirstAndLastIntegrationActivityManager extends AbstractActivityMana SettableFuture> reportCompletedFuture = SettableFuture.create(); states.compute(activityKey, (key, activityStateWrapper) -> { if (activityStateWrapper == null) { - ActivityState activityState = newStateSupplier.get(); - activityState.setLastRecordedTime(newLastRecordedTime); - activityState.setLastReportedTime(0L); activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(activityState); - activityStateWrapper.setAlreadyBeenReported(false); - } else { - activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); + activityStateWrapper.setState(newStateSupplier.get()); + } + var activityState = activityStateWrapper.getState(); + if (activityState.getLastRecordedTime() < newLastRecordedTime) { + activityState.setLastRecordedTime(newLastRecordedTime); } if (activityStateWrapper.isAlreadyBeenReported()) { return activityStateWrapper; } - var activityState = activityStateWrapper.getState(); if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override @@ -107,36 +104,36 @@ public class FirstAndLastIntegrationActivityManager extends AbstractActivityMana @Override public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event in a period for device with id: [{}]", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}] Failed to report first activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); } }, MoreExecutors.directExecutor()); } @Override protected void onReportingPeriodEnd() { - long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityStateWrapper = entry.getValue(); var activityState = activityStateWrapper.getState(); - if (activityState.getLastRecordedTime() < expirationTime) { + long lastRecordedTime = activityState.getLastRecordedTime(); + // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks + if (!activityStateWrapper.isAlreadyBeenReported()) { states.remove(activityKey); } - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.report(activityKey, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + if (activityState.getLastReportedTime() < lastRecordedTime) { + reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - updateLastReportedTime(key, reportedTime); + public void onSuccess(IntegrationActivityKey key, long newLastReportedTime) { + updateLastReportedTime(key, newLastReportedTime); } @Override public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for device with id: [{}]", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}] Failed to report last activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); } }); } activityStateWrapper.setAlreadyBeenReported(false); - activityState.getReportsCount().set(0); } } diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java index b9965098df..38f14f538a 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java @@ -70,19 +70,16 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager SettableFuture> reportCompletedFuture = SettableFuture.create(); states.compute(activityKey, (key, activityStateWrapper) -> { if (activityStateWrapper == null) { - ActivityState activityState = newStateSupplier.get(); - activityState.setLastRecordedTime(newLastRecordedTime); - activityState.setLastReportedTime(0L); activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(activityState); - activityStateWrapper.setAlreadyBeenReported(false); - } else { - activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); + activityStateWrapper.setState(newStateSupplier.get()); + } + var activityState = activityStateWrapper.getState(); + if (activityState.getLastRecordedTime() < newLastRecordedTime) { + activityState.setLastRecordedTime(newLastRecordedTime); } if (activityStateWrapper.isAlreadyBeenReported()) { return activityStateWrapper; } - var activityState = activityStateWrapper.getState(); if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override @@ -107,40 +104,46 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager @Override public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event in a period for device with id: [{}]", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}] Failed to report first activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); } }, MoreExecutors.directExecutor()); } - private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityStateWrapper) -> { - var activityState = activityStateWrapper.getState(); - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityStateWrapper; - }); - } - @Override protected void onReportingPeriodEnd() { for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityStateWrapper = entry.getValue(); var activityState = activityStateWrapper.getState(); - if (!activityStateWrapper.isAlreadyBeenReported() && activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.report(activityKey, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - states.remove(key); - } + long lastRecordedTime = activityState.getLastRecordedTime(); + // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks + if (!activityStateWrapper.isAlreadyBeenReported()) { + states.remove(activityKey); + // report leftover events + if (activityState.getLastReportedTime() < lastRecordedTime) { + reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + updateLastReportedTime(key, reportedTime); // just in case the same key was added again + } - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for device with id: [{}]", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); - } - }); + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + log.debug("[{}] Failed to report last activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + } + }); + } } activityStateWrapper.setAlreadyBeenReported(false); } } + private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { + states.computeIfPresent(key, (__, activityStateWrapper) -> { + var activityState = activityStateWrapper.getState(); + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityStateWrapper; + }); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java index bffdc49054..ef6fa4f8b6 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java @@ -55,9 +55,8 @@ public class LastOnlyIntegrationActivityManager extends AbstractActivityManager< states.compute(activityKey, (__, activityState) -> { if (activityState == null) { activityState = newStateSupplier.get(); - activityState.setLastRecordedTime(newLastRecordedTime); - activityState.setLastReportedTime(0L); - } else { + } + if (activityState.getLastRecordedTime() < newLastRecordedTime) { activityState.setLastRecordedTime(newLastRecordedTime); } return activityState; @@ -70,11 +69,13 @@ public class LastOnlyIntegrationActivityManager extends AbstractActivityManager< for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityState = entry.getValue(); - if (activityState.getLastRecordedTime() < expirationTime) { + long lastRecordedTime = activityState.getLastRecordedTime(); + // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks + if (lastRecordedTime < expirationTime) { states.remove(activityKey); } - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - reporter.report(activityKey, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + if (activityState.getLastReportedTime() < lastRecordedTime) { + reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { updateLastReportedTime(key, reportedTime); @@ -82,7 +83,7 @@ public class LastOnlyIntegrationActivityManager extends AbstractActivityManager< @Override public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for device with id: [{}]", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}] Failed to report last activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); } }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 0c7e00816a..4dc84037a8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -30,7 +30,9 @@ */ package org.thingsboard.server.common.transport.activity; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.StringUtils; import java.util.Objects; import java.util.Random; @@ -39,52 +41,44 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +@Slf4j public abstract class AbstractActivityManager implements ActivityManager { private String name; protected long reportingPeriodMillis; protected ActivityStateReporter reporter; private ScheduledExecutorService scheduler; - protected boolean initialized; + private boolean initialized; @Override - public synchronized void init() { - Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided activity reporter is null."); - if (reportingPeriodMillis < 1000) { - throw new IllegalArgumentException("Failed to initialize activity manager: provided reporting period duration is less that 1 second."); + public synchronized void init(String name, long reportingPeriodMillis, ActivityStateReporter reporter) { + this.name = StringUtils.notBlankOrDefault(name, "activity-manager"); + this.reporter = Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided activity reporter is null."); + if (reportingPeriodMillis <= 0) { + reportingPeriodMillis = 3000; + log.error("[{}] Negative or zero reporting period millisecond was provided. Going to use reporting period value of 3 seconds.", this.name); } + this.reportingPeriodMillis = reportingPeriodMillis; if (!initialized) { - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(name == null ? "activity-manager" : name)); + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(this.name)); scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodMillis), reportingPeriodMillis, TimeUnit.MILLISECONDS); initialized = true; } } - @Override - public void setName(String name) { - this.name = name; - } - - @Override - public void setReportingPeriod(long reportingPeriodMillis) { - this.reportingPeriodMillis = reportingPeriodMillis; - } - - @Override - public void setActivityReporter(ActivityStateReporter activityReporter) { - reporter = activityReporter; - } - @Override public void onActivity(Key key, Supplier newStateSupplier) { if (!initialized) { - throw new IllegalStateException(name + " is not initialized."); + log.error("[{}] Failed to process activity event: activity manager is not initialized.", name); + return; } if (key == null) { - throw new IllegalArgumentException("Activity key can't be null."); + log.error("[{}] Failed to process activity event: provided activity key is null.", name); + return; } if (newStateSupplier == null) { - throw new IllegalArgumentException("New activity state supplier can't be null."); + log.error("[{}] Failed to process activity event: provided new activity state supplier is null.", name); + return; } doOnActivity(key, newStateSupplier); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index ba09ccf773..8f006003ef 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -34,13 +34,7 @@ import java.util.function.Supplier; public interface ActivityManager { - void setName(String name); - - void setReportingPeriod(long reportingPeriodMillis); - - void setActivityReporter(ActivityStateReporter activityReporter); - - void init(); + void init(String name, long reportingPeriodMillis, ActivityStateReporter reporter); void onActivity(Key key, Supplier newStateSupplier); From a941deaef9306e8a7c214a2fa3fd23545565be47 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 8 Dec 2023 11:12:24 +0200 Subject: [PATCH 07/31] [WIP] Refactor transport activity managers. Add telemetry TTL for device state. --- .../state/DefaultDeviceStateService.java | 8 +++- .../src/main/resources/thingsboard.yml | 2 + .../service/DefaultTransportService.java | 6 +-- .../FirstAndLastTransportActivityManager.java | 44 ++++++++----------- .../FirstOnlyTransportActivityManager.java | 44 ++++++++----------- .../LastOnlyTransportActivityManager.java | 34 ++++++-------- 6 files changed, 59 insertions(+), 79 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 37070718a3..e010cf3050 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -185,6 +185,10 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService deviceStates = new ConcurrentHashMap<>(); @@ -803,7 +807,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); + telemetryTtl, new TelemetrySaveCallback<>(deviceId, key, value)); } else { tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } @@ -814,7 +818,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService(deviceId, key, value)); + telemetryTtl, new TelemetrySaveCallback<>(deviceId, key, value)); } else { tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 73f0622cd1..16af7b1618 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -785,6 +785,8 @@ state: # If 'persistToTelemetry' is changed from 'false' to 'true': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_ts_view;' # If 'persistToTelemetry' is changed from 'true' to 'false': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view;' persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" + # Time-to-live for device state telemetry data (e.g. 'active', 'lastActivityTime'). Used only when state.persistToTelemetry is set to 'true'. + telemetryTtl: "${STATE_TELEMETRY_TTL:0}" # Tbel parameters tbel: diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 6453cbd24c..692421b0ab 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -255,11 +255,7 @@ public class DefaultTransportService implements TransportService { transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); - - activityManager.setName("transport-activity-manager"); - activityManager.setReportingPeriod(sessionReportTimeout); - activityManager.setActivityReporter(activityReporter); - activityManager.init(); + activityManager.init("transport-activity-manager", sessionReportTimeout, activityReporter); } private final ActivityStateReporter activityReporter = (sessionId, timeToReport, state, reportCallback) -> { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java index e6bffa4dfd..6370272813 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java @@ -43,14 +43,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.util.Pair; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; +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; @@ -86,19 +87,16 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage SettableFuture> reportCompletedFuture = SettableFuture.create(); states.compute(sessionId, (key, activityStateWrapper) -> { if (activityStateWrapper == null) { - TransportActivityState activityState = newStateSupplier.get(); - activityState.setLastRecordedTime(newLastRecordedTime); - activityState.setLastReportedTime(0L); activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(activityState); - activityStateWrapper.setAlreadyBeenReported(false); - } else { - activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); + activityStateWrapper.setState(newStateSupplier.get()); + } + var activityState = activityStateWrapper.getState(); + if (activityState.getLastRecordedTime() < newLastRecordedTime) { + activityState.setLastRecordedTime(newLastRecordedTime); } if (activityStateWrapper.isAlreadyBeenReported()) { return activityStateWrapper; } - var activityState = activityStateWrapper.getState(); if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override @@ -123,14 +121,14 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage @Override public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event in a period for session.", sessionId); + log.debug("[{}] Failed to report first activity event in a period.", sessionId); } }, MoreExecutors.directExecutor()); } @Override protected void onReportingPeriodEnd() { - long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; + Set statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -140,7 +138,7 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage if (sessionMetaData != null) { activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); } else { - states.remove(sessionId); + statesToRemove.add(sessionId); } long lastActivityTime = activityState.getLastRecordedTime(); @@ -157,25 +155,18 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage } } + long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; if (hasExpired) { if (log.isDebugEnabled()) { - log.debug("[{}] Session has expired due to last activity time: [{}]!", sessionId, lastActivityTime); + log.debug("[{}] Session has expired due to last activity time: [{}].", sessionId, lastActivityTime); } + statesToRemove.add(sessionId); transportService.deregisterSession(sessionInfo); - transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, new TransportServiceCallback<>() { - @Override - public void onSuccess(Void msgAcknowledged) { - states.remove(sessionId); - } - - @Override - public void onError(Throwable e) { - states.remove(sessionId); - } - }); + transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } else if (activityState.getLastReportedTime() < lastActivityTime) { + } + if (activityState.getLastReportedTime() < lastActivityTime) { reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -184,12 +175,13 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage @Override public void onFailure(UUID key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for session.", sessionId); + log.debug("[{}] Failed to report last activity event in a period.", sessionId); } }); } activityStateWrapper.setAlreadyBeenReported(false); } + statesToRemove.forEach(states::remove); } private void updateLastReportedTime(UUID key, long newLastReportedTime) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java index 8cefbb0b46..768796b2be 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java @@ -43,14 +43,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.util.Pair; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; +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; @@ -86,19 +87,16 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager> reportCompletedFuture = SettableFuture.create(); states.compute(sessionId, (key, activityStateWrapper) -> { if (activityStateWrapper == null) { - TransportActivityState activityState = newStateSupplier.get(); - activityState.setLastRecordedTime(newLastRecordedTime); - activityState.setLastReportedTime(0L); activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(activityState); - activityStateWrapper.setAlreadyBeenReported(false); - } else { - activityStateWrapper.getState().setLastRecordedTime(newLastRecordedTime); + activityStateWrapper.setState(newStateSupplier.get()); + } + var activityState = activityStateWrapper.getState(); + if (activityState.getLastRecordedTime() < newLastRecordedTime) { + activityState.setLastRecordedTime(newLastRecordedTime); } if (activityStateWrapper.isAlreadyBeenReported()) { return activityStateWrapper; } - var activityState = activityStateWrapper.getState(); if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override @@ -123,14 +121,14 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -140,7 +138,7 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager() { - @Override - public void onSuccess(Void msgAcknowledged) { - states.remove(sessionId); - } - - @Override - public void onError(Throwable e) { - states.remove(sessionId); - } - }); + transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); } - if ((sessionMetaData == null || hasExpired) && activityState.getLastReportedTime() < lastActivityTime) { + boolean shouldReportLeftoverEvents = sessionMetaData == null || hasExpired; + if (shouldReportLeftoverEvents && activityState.getLastReportedTime() < lastActivityTime) { reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -185,12 +176,13 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager { if (activityState == null) { activityState = newStateSupplier.get(); - activityState.setLastRecordedTime(newLastRecordedTime); - activityState.setLastReportedTime(0L); - } else { + } + if (activityState.getLastRecordedTime() < newLastRecordedTime) { activityState.setLastRecordedTime(newLastRecordedTime); } return activityState; @@ -82,7 +82,7 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityState = entry.getValue(); @@ -91,7 +91,7 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager() { - @Override - public void onSuccess(Void msgAcknowledged) { - states.remove(sessionId); - } - - @Override - public void onError(Throwable e) { - states.remove(sessionId); - } - }); + transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } else if (activityState.getLastReportedTime() < lastActivityTime) { + } + if (activityState.getLastReportedTime() < lastActivityTime) { reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -135,11 +128,12 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager Date: Fri, 8 Dec 2023 12:32:13 +0200 Subject: [PATCH 08/31] Add logging --- ...irstAndLastIntegrationActivityManager.java | 13 +++++++--- .../FirstOnlyIntegrationActivityManager.java | 13 +++++++--- .../LastOnlyIntegrationActivityManager.java | 7 +++-- .../activity/AbstractActivityManager.java | 26 ++++++++++++------- .../service/DefaultTransportService.java | 4 ++- .../FirstAndLastTransportActivityManager.java | 14 +++++----- .../FirstOnlyTransportActivityManager.java | 14 +++++----- .../LastOnlyTransportActivityManager.java | 11 ++++---- 8 files changed, 67 insertions(+), 35 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java index e0dcc97ac0..c9d05eaa26 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java @@ -81,6 +81,8 @@ public class FirstAndLastIntegrationActivityManager extends AbstractActivityMana return activityStateWrapper; } if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + log.debug("[{}][{}] Going to report first activity event for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { @@ -104,13 +106,14 @@ public class FirstAndLastIntegrationActivityManager extends AbstractActivityMana @Override public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}][{}] Failed to report first activity event for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); } }, MoreExecutors.directExecutor()); } @Override - protected void onReportingPeriodEnd() { + protected void doOnReportingPeriodEnd() { for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -118,9 +121,12 @@ public class FirstAndLastIntegrationActivityManager extends AbstractActivityMana long lastRecordedTime = activityState.getLastRecordedTime(); // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks if (!activityStateWrapper.isAlreadyBeenReported()) { + log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); states.remove(activityKey); } if (activityState.getLastReportedTime() < lastRecordedTime) { + log.debug("[{}][{}] Going to report last activity event for device with id: [{}].", activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long newLastReportedTime) { @@ -129,7 +135,8 @@ public class FirstAndLastIntegrationActivityManager extends AbstractActivityMana @Override public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); } }); } diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java index 38f14f538a..35567847a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java @@ -81,6 +81,8 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager return activityStateWrapper; } if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + log.debug("[{}][{}] Going to report first activity event for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { @@ -104,13 +106,14 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager @Override public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}][{}] Failed to report activity event for device with id: [{}].", + name, activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); } }, MoreExecutors.directExecutor()); } @Override - protected void onReportingPeriodEnd() { + protected void doOnReportingPeriodEnd() { for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -118,9 +121,12 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager long lastRecordedTime = activityState.getLastRecordedTime(); // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks if (!activityStateWrapper.isAlreadyBeenReported()) { + log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); states.remove(activityKey); // report leftover events if (activityState.getLastReportedTime() < lastRecordedTime) { + log.debug("[{}][{}] Going to report leftover activity event for device with id: [{}].", activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { @Override public void onSuccess(IntegrationActivityKey key, long reportedTime) { @@ -129,7 +135,8 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager @Override public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); } }); } diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java index ef6fa4f8b6..6517a1af2a 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java @@ -64,7 +64,7 @@ public class LastOnlyIntegrationActivityManager extends AbstractActivityManager< } @Override - public void onReportingPeriodEnd() { + public void doOnReportingPeriodEnd() { long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); @@ -72,6 +72,8 @@ public class LastOnlyIntegrationActivityManager extends AbstractActivityManager< long lastRecordedTime = activityState.getLastRecordedTime(); // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks if (lastRecordedTime < expirationTime) { + log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); states.remove(activityKey); } if (activityState.getLastReportedTime() < lastRecordedTime) { @@ -83,7 +85,8 @@ public class LastOnlyIntegrationActivityManager extends AbstractActivityManager< @Override public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for device with id: [{}].", activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); } }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 4dc84037a8..9d7b5c4e19 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -44,7 +44,7 @@ import java.util.function.Supplier; @Slf4j public abstract class AbstractActivityManager implements ActivityManager { - private String name; + protected String name; protected long reportingPeriodMillis; protected ActivityStateReporter reporter; private ScheduledExecutorService scheduler; @@ -52,17 +52,19 @@ public abstract class AbstractActivityManager @Override public synchronized void init(String name, long reportingPeriodMillis, ActivityStateReporter reporter) { - this.name = StringUtils.notBlankOrDefault(name, "activity-manager"); - this.reporter = Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided activity reporter is null."); - if (reportingPeriodMillis <= 0) { - reportingPeriodMillis = 3000; - log.error("[{}] Negative or zero reporting period millisecond was provided. Going to use reporting period value of 3 seconds.", this.name); - } - this.reportingPeriodMillis = reportingPeriodMillis; if (!initialized) { + log.info("[{}] initializing.", this.name); + this.name = StringUtils.notBlankOrDefault(name, "activity-manager"); + this.reporter = Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided activity reporter is null."); + if (reportingPeriodMillis <= 0) { + reportingPeriodMillis = 3000; + log.error("[{}] Negative or zero reporting period millisecond was provided. Going to use reporting period value of 3 seconds.", this.name); + } + this.reportingPeriodMillis = reportingPeriodMillis; scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(this.name)); scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodMillis), reportingPeriodMillis, TimeUnit.MILLISECONDS); initialized = true; + log.info("[{}] initialized.", this.name); } } @@ -80,12 +82,18 @@ public abstract class AbstractActivityManager log.error("[{}] Failed to process activity event: provided new activity state supplier is null.", name); return; } + log.debug("[{}] Received activity event for key: [{}]", name, key); doOnActivity(key, newStateSupplier); } protected abstract void doOnActivity(Key key, Supplier newStateSupplier); - protected abstract void onReportingPeriodEnd(); + private void onReportingPeriodEnd() { + log.debug("[{}] Going to end reporting period.", name); + doOnReportingPeriodEnd(); + } + + protected abstract void doOnReportingPeriodEnd(); @Override public synchronized void destroy() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 692421b0ab..d2fa8ad8ca 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -187,6 +187,7 @@ public class DefaultTransportService implements TransportService { private final NotificationRuleProcessor notificationRuleProcessor; private final EntityLimitsCache entityLimitsCache; private ActivityManager activityManager; + private static final String ACTIVITY_MANAGER_NAME = "transport-activity-manager"; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -255,10 +256,11 @@ public class DefaultTransportService implements TransportService { transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); - activityManager.init("transport-activity-manager", sessionReportTimeout, activityReporter); + activityManager.init(ACTIVITY_MANAGER_NAME, sessionReportTimeout, activityReporter); } private final ActivityStateReporter activityReporter = (sessionId, timeToReport, state, reportCallback) -> { + log.debug("[{}] Reporting activity state for key: [{}]. Time to report: [{}].", ACTIVITY_MANAGER_NAME, sessionId, timeToReport); SessionMetaData sessionMetaData = sessions.get(sessionId); TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java index 6370272813..db0ebc7ddb 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java @@ -98,6 +98,7 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage return activityStateWrapper; } if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + log.debug("[{}] Going to report first activity event for session with id: [{}]", name, sessionId); reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -121,13 +122,13 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage @Override public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event in a period.", sessionId); + log.debug("[{}] Failed to report first activity event for session with id: [{}]", name, sessionId); } }, MoreExecutors.directExecutor()); } @Override - protected void onReportingPeriodEnd() { + protected void doOnReportingPeriodEnd() { Set statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); @@ -138,6 +139,7 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage if (sessionMetaData != null) { activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); } else { + log.debug("[{}] Session with id: [{}] is not present. Marking it's activity state for removal.", name, sessionId); statesToRemove.add(sessionId); } @@ -150,6 +152,7 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { ActivityStateWrapper gwActivityStateWrapper = states.get(gwSessionId); if (gwActivityStateWrapper != null) { + log.debug("[{}] Session with id: [{}] has gateway session with id: [{}] with overwrite activity time enabled. Updating last activity time.", name, sessionId, gwSessionId); lastActivityTime = Math.max(gwActivityStateWrapper.getState().getLastRecordedTime(), lastActivityTime); } } @@ -158,15 +161,14 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; if (hasExpired) { - if (log.isDebugEnabled()) { - log.debug("[{}] Session has expired due to last activity time: [{}].", sessionId, lastActivityTime); - } + log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Marking it's activity state for removal.", name, sessionId, lastActivityTime); statesToRemove.add(sessionId); transportService.deregisterSession(sessionInfo); transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); } if (activityState.getLastReportedTime() < lastActivityTime) { + log.debug("[{}] Going to report last activity event for session with id: [{}].", name, sessionId); reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -175,7 +177,7 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage @Override public void onFailure(UUID key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period.", sessionId); + log.debug("[{}] Failed to report last activity event for session with id: [{}].", name, sessionId); } }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java index 768796b2be..b0762e0b5e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java @@ -98,6 +98,7 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -121,13 +122,13 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); @@ -138,6 +139,7 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -176,7 +178,7 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); @@ -91,6 +91,7 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager() { @Override public void onSuccess(UUID key, long reportedTime) { @@ -128,7 +129,7 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager Date: Fri, 8 Dec 2023 13:33:49 +0200 Subject: [PATCH 09/31] Change transport reporting strategy to be backward compatible --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 16af7b1618..e25d2ce3ba 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -874,7 +874,7 @@ transport: # - 'first': Only the first activity event in each reporting period is reported. # - 'last': Only the last activity event in the reporting period is reported. # - 'first-and-last': Both the first and last activity events in the reporting period are reported. - reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:first-and-last}" + reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:last}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" From 84c22dc4a3050dd35662bb0146b39a18c8ac44de Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 8 Dec 2023 13:53:29 +0200 Subject: [PATCH 10/31] Add 'all' reporting strategy for integrations --- .../AllIntegrationActivityManager.java | 122 ++++++++++++++++++ .../FirstOnlyIntegrationActivityManager.java | 2 +- .../LastOnlyIntegrationActivityManager.java | 2 +- .../src/main/resources/thingsboard.yml | 3 +- 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java new file mode 100644 index 0000000000..de9b132896 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java @@ -0,0 +1,122 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.service.integration.activity; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.util.Pair; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityState; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Supplier; + +@Slf4j +@Component +@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "all") +public class AllIntegrationActivityManager extends AbstractActivityManager { + + private final ConcurrentMap states = new ConcurrentHashMap<>(); + + @Override + protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + SettableFuture> reportCompletedFuture = SettableFuture.create(); + states.compute(activityKey, (key, activityState) -> { + if (activityState == null) { + activityState = newStateSupplier.get(); + } + if (activityState.getLastRecordedTime() < newLastRecordedTime) { + activityState.setLastRecordedTime(newLastRecordedTime); + } + if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { + log.debug("[{}][{}] Going to report activity event for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); + reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + reportCompletedFuture.set(Pair.of(key, reportedTime)); + } + + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + reportCompletedFuture.setException(t); + } + }); + } + return activityState; + }); + Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { + @Override + public void onSuccess(Pair reportResult) { + updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); + } + + @Override + public void onFailure(@NonNull Throwable t) { + log.debug("[{}][{}] Failed to report activity event for device with id: [{}].", + name, activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); + } + }, MoreExecutors.directExecutor()); + } + + private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { + states.computeIfPresent(key, (__, activityState) -> { + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityState; + }); + } + + @Override + protected void doOnReportingPeriodEnd() { + for (Map.Entry entry : states.entrySet()) { + var activityKey = entry.getKey(); + var activityState = entry.getValue(); + // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks + long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; + if (activityState.getLastRecordedTime() < expirationTime) { + log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); + states.remove(activityKey); + } + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java index 35567847a5..239fd5f19e 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java @@ -106,7 +106,7 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager @Override public void onFailure(@NonNull Throwable t) { - log.debug("[{}][{}] Failed to report activity event for device with id: [{}].", + log.debug("[{}][{}] Failed to report first activity event for device with id: [{}].", name, activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); } }, MoreExecutors.directExecutor()); diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java index 6517a1af2a..f29b81baf8 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java @@ -65,12 +65,12 @@ public class LastOnlyIntegrationActivityManager extends AbstractActivityManager< @Override public void doOnReportingPeriodEnd() { - long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; for (Map.Entry entry : states.entrySet()) { var activityKey = entry.getKey(); var activityState = entry.getValue(); long lastRecordedTime = activityState.getLastRecordedTime(); // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks + long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; if (lastRecordedTime < expirationTime) { log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e25d2ce3ba..7baf6dbf9b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -874,7 +874,8 @@ transport: # - 'first': Only the first activity event in each reporting period is reported. # - 'last': Only the last activity event in the reporting period is reported. # - 'first-and-last': Both the first and last activity events in the reporting period are reported. - reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:last}" + # - 'all': All activity events in the reporting period are reported. + reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:all}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" From 64ccad0ed2b0f100a90618f7f145a506e3c428ca Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 8 Dec 2023 14:00:36 +0200 Subject: [PATCH 11/31] Fix typo --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 7baf6dbf9b..b47219c89a 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -875,7 +875,7 @@ transport: # - 'last': Only the last activity event in the reporting period is reported. # - 'first-and-last': Both the first and last activity events in the reporting period are reported. # - 'all': All activity events in the reporting period are reported. - reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:all}" + reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:first-and-last}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" From 8b3ad49ed82bbb6a3726f98a9be7b08387981830 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 8 Dec 2023 14:01:40 +0200 Subject: [PATCH 12/31] Change default reporting strategy for transports to last to be backward compatible --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index b47219c89a..be52ee1f0d 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -875,7 +875,7 @@ transport: # - 'last': Only the last activity event in the reporting period is reported. # - 'first-and-last': Both the first and last activity events in the reporting period are reported. # - 'all': All activity events in the reporting period are reported. - reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:first-and-last}" + reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:last}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" From 6d15bf82df25d1a12d3a120601eed60c6729098b Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Sat, 9 Dec 2023 10:05:06 +0200 Subject: [PATCH 13/31] Polishing after review --- .../FirstOnlyIntegrationActivityManager.java | 42 ++++++++++--------- .../src/main/resources/thingsboard.yml | 7 ++-- .../service/DefaultTransportService.java | 4 +- .../FirstAndLastTransportActivityManager.java | 12 ++---- .../FirstOnlyTransportActivityManager.java | 12 ++---- .../LastOnlyTransportActivityManager.java | 12 ++---- 6 files changed, 40 insertions(+), 49 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java index 239fd5f19e..ae03c1fb94 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java @@ -115,31 +115,33 @@ public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager @Override protected void doOnReportingPeriodEnd() { for (Map.Entry entry : states.entrySet()) { - var activityKey = entry.getKey(); var activityStateWrapper = entry.getValue(); + if (activityStateWrapper.isAlreadyBeenReported()) { + activityStateWrapper.setAlreadyBeenReported(false); + continue; + } + var activityKey = entry.getKey(); var activityState = activityStateWrapper.getState(); long lastRecordedTime = activityState.getLastRecordedTime(); // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks - if (!activityStateWrapper.isAlreadyBeenReported()) { - log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - states.remove(activityKey); - // report leftover events - if (activityState.getLastReportedTime() < lastRecordedTime) { - log.debug("[{}][{}] Going to report leftover activity event for device with id: [{}].", activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - updateLastReportedTime(key, reportedTime); // just in case the same key was added again - } + log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); + states.remove(activityKey); + // report leftover events + if (activityState.getLastReportedTime() < lastRecordedTime) { + log.debug("[{}][{}] Going to report leftover activity event for device with id: [{}].", activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); + reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(IntegrationActivityKey key, long reportedTime) { + updateLastReportedTime(key, reportedTime); // just in case the same key was added again + } - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - } - }); - } + @Override + public void onFailure(IntegrationActivityKey key, Throwable t) { + log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", + activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); + } + }); } activityStateWrapper.setAlreadyBeenReported(false); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index be52ee1f0d..57e55f4eda 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -785,7 +785,9 @@ state: # If 'persistToTelemetry' is changed from 'false' to 'true': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_ts_view;' # If 'persistToTelemetry' is changed from 'true' to 'false': 'CREATE OR REPLACE VIEW device_info_view AS SELECT * FROM device_info_active_attribute_view;' persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" - # Time-to-live for device state telemetry data (e.g. 'active', 'lastActivityTime'). Used only when state.persistToTelemetry is set to 'true'. + # Millisecond value defining time-to-live for device state telemetry data (e.g. 'active', 'lastActivityTime'). + # Used only when state.persistToTelemetry is set to 'true' and Cassandra is used for timeseries data. + # 0 means time-to-live mechanism is disabled. telemetryTtl: "${STATE_TELEMETRY_TTL:0}" # Tbel parameters @@ -870,11 +872,10 @@ transport: report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" activity: # This property specifies the strategy for reporting activity events within each reporting period. - # The accepted values are 'first', 'last', and 'first and last'. + # The accepted values are 'first', 'last', and 'first-and-last'. # - 'first': Only the first activity event in each reporting period is reported. # - 'last': Only the last activity event in the reporting period is reported. # - 'first-and-last': Both the first and last activity events in the reporting period are reported. - # - 'all': All activity events in the reporting period are reported. reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:last}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index d2fa8ad8ca..a86bdda312 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -136,6 +136,7 @@ public class DefaultTransportService implements TransportService { public static final String OVERWRITE_ACTIVITY_TIME = "overwriteActivityTime"; public static final String SESSION_EXPIRED_MESSAGE = "Session has expired due to last activity time!"; + private static final String ACTIVITY_MANAGER_NAME = "transport-activity-manager"; public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_OPEN = getSessionEventMsg(TransportProtos.SessionEvent.OPEN); public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_CLOSED = getSessionEventMsg(TransportProtos.SessionEvent.CLOSED); public static final TransportProtos.SessionCloseNotificationProto SESSION_EXPIRED_NOTIFICATION_PROTO = TransportProtos.SessionCloseNotificationProto.newBuilder() @@ -187,7 +188,6 @@ public class DefaultTransportService implements TransportService { private final NotificationRuleProcessor notificationRuleProcessor; private final EntityLimitsCache entityLimitsCache; private ActivityManager activityManager; - private static final String ACTIVITY_MANAGER_NAME = "transport-activity-manager"; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -260,7 +260,7 @@ public class DefaultTransportService implements TransportService { } private final ActivityStateReporter activityReporter = (sessionId, timeToReport, state, reportCallback) -> { - log.debug("[{}] Reporting activity state for key: [{}]. Time to report: [{}].", ACTIVITY_MANAGER_NAME, sessionId, timeToReport); + log.debug("[{}] Reporting activity state for session with id: [{}]. Time to report: [{}].", ACTIVITY_MANAGER_NAME, sessionId, timeToReport); SessionMetaData sessionMetaData = sessions.get(sessionId); TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java index db0ebc7ddb..f14d958307 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java @@ -49,9 +49,7 @@ import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; -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; @@ -129,7 +127,6 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage @Override protected void doOnReportingPeriodEnd() { - Set statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -139,8 +136,8 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage if (sessionMetaData != null) { activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); } else { - log.debug("[{}] Session with id: [{}] is not present. Marking it's activity state for removal.", name, sessionId); - statesToRemove.add(sessionId); + log.debug("[{}] Session with id: [{}] is not present. Removing it's activity state.", name, sessionId); + states.remove(sessionId); } long lastActivityTime = activityState.getLastRecordedTime(); @@ -161,8 +158,8 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; if (hasExpired) { - log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Marking it's activity state for removal.", name, sessionId, lastActivityTime); - statesToRemove.add(sessionId); + log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Removing it's activity state.", name, sessionId, lastActivityTime); + states.remove(sessionId); transportService.deregisterSession(sessionInfo); transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); @@ -183,7 +180,6 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage } activityStateWrapper.setAlreadyBeenReported(false); } - statesToRemove.forEach(states::remove); } private void updateLastReportedTime(UUID key, long newLastReportedTime) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java index b0762e0b5e..34378caeb6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java @@ -49,9 +49,7 @@ import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; -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; @@ -129,7 +127,6 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -139,8 +136,8 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityState = entry.getValue(); @@ -91,8 +88,8 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager Date: Sat, 9 Dec 2023 10:10:50 +0200 Subject: [PATCH 14/31] Add @TbCoreComponent and @TbTransportComponent --- .../integration/activity/AllIntegrationActivityManager.java | 2 ++ .../activity/FirstAndLastIntegrationActivityManager.java | 2 ++ .../activity/FirstOnlyIntegrationActivityManager.java | 2 ++ .../activity/LastOnlyIntegrationActivityManager.java | 2 ++ .../service/activity/FirstAndLastTransportActivityManager.java | 2 ++ .../service/activity/FirstOnlyTransportActivityManager.java | 2 ++ .../service/activity/LastOnlyTransportActivityManager.java | 2 ++ 7 files changed, 14 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java index de9b132896..5cb8d1ea38 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java @@ -42,6 +42,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -50,6 +51,7 @@ import java.util.function.Supplier; @Slf4j @Component +@TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "all") public class AllIntegrationActivityManager extends AbstractActivityManager { diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java index c9d05eaa26..785e6f4155 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java @@ -43,6 +43,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -51,6 +52,7 @@ import java.util.function.Supplier; @Slf4j @Component +@TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first-and-last") public class FirstAndLastIntegrationActivityManager extends AbstractActivityManager { diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java index ae03c1fb94..d99745af0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java @@ -43,6 +43,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -51,6 +52,7 @@ import java.util.function.Supplier; @Slf4j @Component +@TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first") public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager { diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java index f29b81baf8..c90b4f86e5 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java @@ -36,6 +36,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -44,6 +45,7 @@ import java.util.function.Supplier; @Slf4j @Component +@TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "last") public class LastOnlyIntegrationActivityManager extends AbstractActivityManager { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java index f14d958307..2cc17a532e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java @@ -48,6 +48,7 @@ import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Map; import java.util.UUID; @@ -60,6 +61,7 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe @Slf4j @Component +@TbTransportComponent @ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first-and-last") public class FirstAndLastTransportActivityManager extends AbstractActivityManager { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java index 34378caeb6..82d6fcccc8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java @@ -48,6 +48,7 @@ import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Map; import java.util.UUID; @@ -60,6 +61,7 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe @Slf4j @Component +@TbTransportComponent @ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first") public class FirstOnlyTransportActivityManager extends AbstractActivityManager { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java index 056ace10b5..85db15b1ec 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/LastOnlyTransportActivityManager.java @@ -41,6 +41,7 @@ import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Map; import java.util.UUID; @@ -53,6 +54,7 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe @Slf4j @Component +@TbTransportComponent @ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "last") public class LastOnlyTransportActivityManager extends AbstractActivityManager { From cfdceb9ae005a0e191618e0245bdb32d8191f239 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 11 Dec 2023 13:15:47 +0200 Subject: [PATCH 15/31] Change activity manager names, remove activity after iteration on period end --- ...> AllEventsIntegrationActivityManager.java} | 2 +- ...dLastEventsIntegrationActivityManager.java} | 2 +- ...stEventOnlyIntegrationActivityManager.java} | 2 +- ...stEventOnlyIntegrationActivityManager.java} | 2 +- .../activity/AbstractActivityManager.java | 2 +- ...tAndLastEventTransportActivityManager.java} | 14 +++++++++----- ...nlyFirstEventTransportActivityManager.java} | 14 +++++++++----- ...OnlyLastEventTransportActivityManager.java} | 18 +++++++++++------- 8 files changed, 34 insertions(+), 22 deletions(-) rename application/src/main/java/org/thingsboard/server/service/integration/activity/{AllIntegrationActivityManager.java => AllEventsIntegrationActivityManager.java} (98%) rename application/src/main/java/org/thingsboard/server/service/integration/activity/{FirstAndLastIntegrationActivityManager.java => FirstAndLastEventsIntegrationActivityManager.java} (98%) rename application/src/main/java/org/thingsboard/server/service/integration/activity/{FirstOnlyIntegrationActivityManager.java => FirstEventOnlyIntegrationActivityManager.java} (98%) rename application/src/main/java/org/thingsboard/server/service/integration/activity/{LastOnlyIntegrationActivityManager.java => LastEventOnlyIntegrationActivityManager.java} (97%) rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/{FirstAndLastTransportActivityManager.java => FirstAndLastEventTransportActivityManager.java} (94%) rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/{FirstOnlyTransportActivityManager.java => OnlyFirstEventTransportActivityManager.java} (94%) rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/{LastOnlyTransportActivityManager.java => OnlyLastEventTransportActivityManager.java} (90%) diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/AllEventsIntegrationActivityManager.java similarity index 98% rename from application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java rename to application/src/main/java/org/thingsboard/server/service/integration/activity/AllEventsIntegrationActivityManager.java index 5cb8d1ea38..f2edb20a7f 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/AllIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/AllEventsIntegrationActivityManager.java @@ -53,7 +53,7 @@ import java.util.function.Supplier; @Component @TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "all") -public class AllIntegrationActivityManager extends AbstractActivityManager { +public class AllEventsIntegrationActivityManager extends AbstractActivityManager { private final ConcurrentMap states = new ConcurrentHashMap<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastEventsIntegrationActivityManager.java similarity index 98% rename from application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java rename to application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastEventsIntegrationActivityManager.java index 785e6f4155..1c2dba809e 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastEventsIntegrationActivityManager.java @@ -54,7 +54,7 @@ import java.util.function.Supplier; @Component @TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first-and-last") -public class FirstAndLastIntegrationActivityManager extends AbstractActivityManager { +public class FirstAndLastEventsIntegrationActivityManager extends AbstractActivityManager { private final ConcurrentMap states = new ConcurrentHashMap<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstEventOnlyIntegrationActivityManager.java similarity index 98% rename from application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java rename to application/src/main/java/org/thingsboard/server/service/integration/activity/FirstEventOnlyIntegrationActivityManager.java index d99745af0a..24f9a8f388 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstEventOnlyIntegrationActivityManager.java @@ -54,7 +54,7 @@ import java.util.function.Supplier; @Component @TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first") -public class FirstOnlyIntegrationActivityManager extends AbstractActivityManager { +public class FirstEventOnlyIntegrationActivityManager extends AbstractActivityManager { private final ConcurrentMap states = new ConcurrentHashMap<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastEventOnlyIntegrationActivityManager.java similarity index 97% rename from application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java rename to application/src/main/java/org/thingsboard/server/service/integration/activity/LastEventOnlyIntegrationActivityManager.java index c90b4f86e5..16699b2e3c 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastOnlyIntegrationActivityManager.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastEventOnlyIntegrationActivityManager.java @@ -47,7 +47,7 @@ import java.util.function.Supplier; @Component @TbCoreComponent @ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "last") -public class LastOnlyIntegrationActivityManager extends AbstractActivityManager { +public class LastEventOnlyIntegrationActivityManager extends AbstractActivityManager { private final ConcurrentMap states = new ConcurrentHashMap<>(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 9d7b5c4e19..dd8682aaed 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -53,8 +53,8 @@ public abstract class AbstractActivityManager @Override public synchronized void init(String name, long reportingPeriodMillis, ActivityStateReporter reporter) { if (!initialized) { - log.info("[{}] initializing.", this.name); this.name = StringUtils.notBlankOrDefault(name, "activity-manager"); + log.info("[{}] initializing.", this.name); this.reporter = Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided activity reporter is null."); if (reportingPeriodMillis <= 0) { reportingPeriodMillis = 3000; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastEventTransportActivityManager.java similarity index 94% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastEventTransportActivityManager.java index 2cc17a532e..9c582b2587 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastEventTransportActivityManager.java @@ -50,7 +50,9 @@ import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbTransportComponent; +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; @@ -63,7 +65,7 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe @Component @TbTransportComponent @ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first-and-last") -public class FirstAndLastTransportActivityManager extends AbstractActivityManager { +public class FirstAndLastEventTransportActivityManager extends AbstractActivityManager { private final ConcurrentMap states = new ConcurrentHashMap<>(); @@ -129,6 +131,7 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage @Override protected void doOnReportingPeriodEnd() { + Set statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -138,8 +141,8 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage if (sessionMetaData != null) { activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); } else { - log.debug("[{}] Session with id: [{}] is not present. Removing it's activity state.", name, sessionId); - states.remove(sessionId); + log.debug("[{}] Session with id: [{}] is not present. Marking it's activity state for removal.", name, sessionId); + statesToRemove.add(sessionId); } long lastActivityTime = activityState.getLastRecordedTime(); @@ -160,9 +163,9 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; if (hasExpired) { - log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Removing it's activity state.", name, sessionId, lastActivityTime); - states.remove(sessionId); + log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Marking it's activity state for removal.", name, sessionId, lastActivityTime); transportService.deregisterSession(sessionInfo); + statesToRemove.add(sessionId); transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); } @@ -182,6 +185,7 @@ public class FirstAndLastTransportActivityManager extends AbstractActivityManage } activityStateWrapper.setAlreadyBeenReported(false); } + statesToRemove.forEach(states::remove); } private void updateLastReportedTime(UUID key, long newLastReportedTime) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyFirstEventTransportActivityManager.java similarity index 94% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyFirstEventTransportActivityManager.java index 82d6fcccc8..19a6314450 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstOnlyTransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyFirstEventTransportActivityManager.java @@ -50,7 +50,9 @@ import org.thingsboard.server.common.transport.service.TransportActivityState; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbTransportComponent; +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; @@ -63,7 +65,7 @@ import static org.thingsboard.server.common.transport.service.DefaultTransportSe @Component @TbTransportComponent @ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first") -public class FirstOnlyTransportActivityManager extends AbstractActivityManager { +public class OnlyFirstEventTransportActivityManager extends AbstractActivityManager { private final ConcurrentMap states = new ConcurrentHashMap<>(); @@ -129,6 +131,7 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityStateWrapper = entry.getValue(); @@ -138,8 +141,8 @@ public class FirstOnlyTransportActivityManager extends AbstractActivityManager { +public class OnlyLastEventTransportActivityManager extends AbstractActivityManager { private final ConcurrentMap states = new ConcurrentHashMap<>(); @@ -67,9 +69,9 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager newStateSupplier) { + protected void doOnActivity(UUID sessionId, Supplier newStateSupplier) { long newLastRecordedTime = System.currentTimeMillis(); - states.compute(activityKey, (__, activityState) -> { + states.compute(sessionId, (__, activityState) -> { if (activityState == null) { activityState = newStateSupplier.get(); } @@ -82,6 +84,7 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager statesToRemove = new HashSet<>(); for (Map.Entry entry : states.entrySet()) { var sessionId = entry.getKey(); var activityState = entry.getValue(); @@ -90,8 +93,8 @@ public class LastOnlyTransportActivityManager extends AbstractActivityManager Date: Thu, 14 Dec 2023 23:14:44 +0200 Subject: [PATCH 16/31] [WIP] Refactoring after review --- .../AllEventsIntegrationActivityManager.java | 124 -------------- ...dLastEventsIntegrationActivityManager.java | 157 ----------------- ...stEventOnlyIntegrationActivityManager.java | 160 ------------------ ...stEventOnlyIntegrationActivityManager.java | 105 ------------ .../src/main/resources/thingsboard.yml | 13 +- .../activity/AbstractActivityManager.java | 103 +++++++++-- .../transport/activity/ActivityManager.java | 16 +- .../transport/activity/ActivityState.java | 5 +- .../activity/strategy/ActivityStrategy.java | 11 ++ .../strategy/ActivityStrategyType.java | 20 +++ .../strategy/AllEventsActivityStrategy.java | 17 ++ .../FirstAndLastEventActivityStrategy.java | 25 +++ .../strategy/FirstEventActivityStrategy.java | 25 +++ .../strategy/LastEventActivityStrategy.java | 17 ++ 14 files changed, 223 insertions(+), 575 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/AllEventsIntegrationActivityManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastEventsIntegrationActivityManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/FirstEventOnlyIntegrationActivityManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/integration/activity/LastEventOnlyIntegrationActivityManager.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/AllEventsIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/AllEventsIntegrationActivityManager.java deleted file mode 100644 index f2edb20a7f..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/AllEventsIntegrationActivityManager.java +++ /dev/null @@ -1,124 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.service.integration.activity; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.util.Pair; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Supplier; - -@Slf4j -@Component -@TbCoreComponent -@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "all") -public class AllEventsIntegrationActivityManager extends AbstractActivityManager { - - private final ConcurrentMap states = new ConcurrentHashMap<>(); - - @Override - protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - SettableFuture> reportCompletedFuture = SettableFuture.create(); - states.compute(activityKey, (key, activityState) -> { - if (activityState == null) { - activityState = newStateSupplier.get(); - } - if (activityState.getLastRecordedTime() < newLastRecordedTime) { - activityState.setLastRecordedTime(newLastRecordedTime); - } - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - log.debug("[{}][{}] Going to report activity event for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - reportCompletedFuture.set(Pair.of(key, reportedTime)); - } - - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - reportCompletedFuture.setException(t); - } - }); - } - return activityState; - }); - Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { - @Override - public void onSuccess(Pair reportResult) { - updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); - } - - @Override - public void onFailure(@NonNull Throwable t) { - log.debug("[{}][{}] Failed to report activity event for device with id: [{}].", - name, activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); - } - }, MoreExecutors.directExecutor()); - } - - private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityState) -> { - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityState; - }); - } - - @Override - protected void doOnReportingPeriodEnd() { - for (Map.Entry entry : states.entrySet()) { - var activityKey = entry.getKey(); - var activityState = entry.getValue(); - // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks - long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; - if (activityState.getLastRecordedTime() < expirationTime) { - log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - states.remove(activityKey); - } - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastEventsIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastEventsIntegrationActivityManager.java deleted file mode 100644 index 1c2dba809e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstAndLastEventsIntegrationActivityManager.java +++ /dev/null @@ -1,157 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.service.integration.activity; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.util.Pair; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Supplier; - -@Slf4j -@Component -@TbCoreComponent -@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first-and-last") -public class FirstAndLastEventsIntegrationActivityManager extends AbstractActivityManager { - - private final ConcurrentMap states = new ConcurrentHashMap<>(); - - @Data - private static class ActivityStateWrapper { - - volatile ActivityState state; - volatile boolean alreadyBeenReported; - - } - - @Override - protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - SettableFuture> reportCompletedFuture = SettableFuture.create(); - states.compute(activityKey, (key, activityStateWrapper) -> { - if (activityStateWrapper == null) { - activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(newStateSupplier.get()); - } - var activityState = activityStateWrapper.getState(); - if (activityState.getLastRecordedTime() < newLastRecordedTime) { - activityState.setLastRecordedTime(newLastRecordedTime); - } - if (activityStateWrapper.isAlreadyBeenReported()) { - return activityStateWrapper; - } - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - log.debug("[{}][{}] Going to report first activity event for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - reportCompletedFuture.set(Pair.of(key, reportedTime)); - } - - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - reportCompletedFuture.setException(t); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(true); - return activityStateWrapper; - }); - Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { - @Override - public void onSuccess(Pair reportResult) { - updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); - } - - @Override - public void onFailure(@NonNull Throwable t) { - log.debug("[{}][{}] Failed to report first activity event for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - } - }, MoreExecutors.directExecutor()); - } - - @Override - protected void doOnReportingPeriodEnd() { - for (Map.Entry entry : states.entrySet()) { - var activityKey = entry.getKey(); - var activityStateWrapper = entry.getValue(); - var activityState = activityStateWrapper.getState(); - long lastRecordedTime = activityState.getLastRecordedTime(); - // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks - if (!activityStateWrapper.isAlreadyBeenReported()) { - log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - states.remove(activityKey); - } - if (activityState.getLastReportedTime() < lastRecordedTime) { - log.debug("[{}][{}] Going to report last activity event for device with id: [{}].", activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long newLastReportedTime) { - updateLastReportedTime(key, newLastReportedTime); - } - - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(false); - } - } - - private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityStateWrapper) -> { - var activityState = activityStateWrapper.getState(); - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityStateWrapper; - }); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstEventOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstEventOnlyIntegrationActivityManager.java deleted file mode 100644 index 24f9a8f388..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/FirstEventOnlyIntegrationActivityManager.java +++ /dev/null @@ -1,160 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.service.integration.activity; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.util.Pair; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Supplier; - -@Slf4j -@Component -@TbCoreComponent -@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "first") -public class FirstEventOnlyIntegrationActivityManager extends AbstractActivityManager { - - private final ConcurrentMap states = new ConcurrentHashMap<>(); - - @Data - private static class ActivityStateWrapper { - - volatile ActivityState state; - volatile boolean alreadyBeenReported; - - } - - @Override - protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - SettableFuture> reportCompletedFuture = SettableFuture.create(); - states.compute(activityKey, (key, activityStateWrapper) -> { - if (activityStateWrapper == null) { - activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(newStateSupplier.get()); - } - var activityState = activityStateWrapper.getState(); - if (activityState.getLastRecordedTime() < newLastRecordedTime) { - activityState.setLastRecordedTime(newLastRecordedTime); - } - if (activityStateWrapper.isAlreadyBeenReported()) { - return activityStateWrapper; - } - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - log.debug("[{}][{}] Going to report first activity event for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - reportCompletedFuture.set(Pair.of(key, reportedTime)); - } - - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - reportCompletedFuture.setException(t); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(true); - return activityStateWrapper; - }); - Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { - @Override - public void onSuccess(Pair reportResult) { - updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); - } - - @Override - public void onFailure(@NonNull Throwable t) { - log.debug("[{}][{}] Failed to report first activity event for device with id: [{}].", - name, activityKey.getTenantId().getId(), activityKey.getDeviceId().getId()); - } - }, MoreExecutors.directExecutor()); - } - - @Override - protected void doOnReportingPeriodEnd() { - for (Map.Entry entry : states.entrySet()) { - var activityStateWrapper = entry.getValue(); - if (activityStateWrapper.isAlreadyBeenReported()) { - activityStateWrapper.setAlreadyBeenReported(false); - continue; - } - var activityKey = entry.getKey(); - var activityState = activityStateWrapper.getState(); - long lastRecordedTime = activityState.getLastRecordedTime(); - // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks - log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - states.remove(activityKey); - // report leftover events - if (activityState.getLastReportedTime() < lastRecordedTime) { - log.debug("[{}][{}] Going to report leftover activity event for device with id: [{}].", activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - updateLastReportedTime(key, reportedTime); // just in case the same key was added again - } - - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(false); - } - } - - private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityStateWrapper) -> { - var activityState = activityStateWrapper.getState(); - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityStateWrapper; - }); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastEventOnlyIntegrationActivityManager.java b/application/src/main/java/org/thingsboard/server/service/integration/activity/LastEventOnlyIntegrationActivityManager.java deleted file mode 100644 index 16699b2e3c..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/integration/activity/LastEventOnlyIntegrationActivityManager.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.service.integration.activity; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Supplier; - -@Slf4j -@Component -@TbCoreComponent -@ConditionalOnProperty(prefix = "integrations.activity", value = "reporting_strategy", havingValue = "last") -public class LastEventOnlyIntegrationActivityManager extends AbstractActivityManager { - - private final ConcurrentMap states = new ConcurrentHashMap<>(); - - @Override - protected void doOnActivity(IntegrationActivityKey activityKey, Supplier newStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - states.compute(activityKey, (__, activityState) -> { - if (activityState == null) { - activityState = newStateSupplier.get(); - } - if (activityState.getLastRecordedTime() < newLastRecordedTime) { - activityState.setLastRecordedTime(newLastRecordedTime); - } - return activityState; - }); - } - - @Override - public void doOnReportingPeriodEnd() { - for (Map.Entry entry : states.entrySet()) { - var activityKey = entry.getKey(); - var activityState = entry.getValue(); - long lastRecordedTime = activityState.getLastRecordedTime(); - // if there were no activities during the reporting period, we should remove the entry to prevent memory leaks - long expirationTime = System.currentTimeMillis() - reportingPeriodMillis; - if (lastRecordedTime < expirationTime) { - log.debug("[{}][{}] No activity events were received during reporting period for device with id: [{}]. Going to remove activity state.", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - states.remove(activityKey); - } - if (activityState.getLastReportedTime() < lastRecordedTime) { - reporter.report(activityKey, lastRecordedTime, activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(IntegrationActivityKey key, long reportedTime) { - updateLastReportedTime(key, reportedTime); - } - - @Override - public void onFailure(IntegrationActivityKey key, Throwable t) { - log.debug("[{}][{}] Failed to report last activity event in a period for device with id: [{}].", - activityKey.getTenantId().getId(), name, activityKey.getDeviceId().getId()); - } - }); - } - } - } - - private void updateLastReportedTime(IntegrationActivityKey key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityState) -> { - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityState; - }); - } - -} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 57e55f4eda..2f710f3e92 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -871,12 +871,13 @@ transport: # Interval of periodic check for expired sessions and report of the changes to session last activity time report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:3000}" activity: - # This property specifies the strategy for reporting activity events within each reporting period. - # The accepted values are 'first', 'last', and 'first-and-last'. - # - 'first': Only the first activity event in each reporting period is reported. - # - 'last': Only the last activity event in the reporting period is reported. - # - 'first-and-last': Both the first and last activity events in the reporting period are reported. - reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:last}" + # This property specifies the strategy for reporting activity events within each reporting period. + # The accepted values are 'FIRST', 'LAST', 'FIRST_AND_LAST' and 'ALL'. + # - 'FIRST': Only the first activity event in each reporting period is reported. + # - 'LAST': Only the last activity event in the reporting period is reported. + # - 'FIRST_AND_LAST': Both the first and last activity events in the reporting period are reported. + # - 'ALL': All activity events in the reporting period are reported. + reporting_strategy: "${TB_TRANSPORT_ACTIVITY_REPORTING_STRATEGY:LAST}" json: # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index dd8682aaed..065a0106f3 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -1,22 +1,22 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * + *

* Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * + *

* NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, * if any. The intellectual and technical concepts contained * herein are proprietary to ThingsBoard, Inc. * and its suppliers and may be covered by U.S. and Foreign Patents, * patents in process, and are protected by trade secret or copyright law. - * + *

* Dissemination of this information or reproduction of this material is strictly forbidden * unless prior written permission is obtained from COMPANY. - * + *

* Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, * managers or contractors who have executed Confidentiality and Non-disclosure agreements * explicitly covering such access. - * + *

* The copyright notice above does not evidence any actual or intended publication * or disclosure of this source code, which includes * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. @@ -33,25 +33,31 @@ package org.thingsboard.server.common.transport.activity; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; +import java.util.Map; import java.util.Objects; import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @Slf4j -public abstract class AbstractActivityManager implements ActivityManager { +public abstract class AbstractActivityManager implements ActivityManager> { - protected String name; - protected long reportingPeriodMillis; - protected ActivityStateReporter reporter; + private final ConcurrentMap> states = new ConcurrentHashMap<>(); + private String name; + private long reportingPeriodMillis; + private ActivityStateReporter> reporter; private ScheduledExecutorService scheduler; private boolean initialized; @Override - public synchronized void init(String name, long reportingPeriodMillis, ActivityStateReporter reporter) { + public synchronized void init(String name, long reportingPeriodMillis, ActivityStateReporter> reporter) { if (!initialized) { this.name = StringUtils.notBlankOrDefault(name, "activity-manager"); log.info("[{}] initializing.", this.name); @@ -69,7 +75,7 @@ public abstract class AbstractActivityManager } @Override - public void onActivity(Key key, Supplier newStateSupplier) { + public void onActivity(Key key, Supplier> newStateSupplier) { if (!initialized) { log.error("[{}] Failed to process activity event: activity manager is not initialized.", name); return; @@ -86,14 +92,83 @@ public abstract class AbstractActivityManager doOnActivity(key, newStateSupplier); } - protected abstract void doOnActivity(Key key, Supplier newStateSupplier); + private boolean validate(Key key, Supplier> newStateSupplier) { + + } + + protected abstract ActivityStrategy getStrategy(); + + private void doOnActivity(Key key, Supplier> newStateSupplier) { + long newLastRecordedTime = System.currentTimeMillis(); + + var shouldReport = new AtomicBoolean(false); + var activityState = states.compute(key, (__, state) -> { + if (state == null) { + state = newStateSupplier.get(); + state.setStrategy(getStrategy()); + } + if (state.getLastRecordedTime() < newLastRecordedTime) { + state.setLastRecordedTime(newLastRecordedTime); + } + shouldReport.set(state.getStrategy().onActivity(state)); + return state; + }); + + if (shouldReport.get()) { + log.debug("[{}] Going to report first activity event for key: [{}].", name, key); + reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + @Override + public void onSuccess(Key key, long reportedTime) { + updateLastReportedTime(key, reportedTime); + } + + @Override + public void onFailure(Key key, Throwable t) { + log.debug("[{}] Failed to report first activity event for key: [{}].", name, key, t); + } + }); + } + } private void onReportingPeriodEnd() { log.debug("[{}] Going to end reporting period.", name); - doOnReportingPeriodEnd(); + for (Map.Entry> entry : states.entrySet()) { + var key = entry.getKey(); + var state = entry.getValue(); + long lastRecordedTime = state.getLastRecordedTime(); + + boolean hasExpired = false; // TODO: implement state expiration + boolean shouldReport; + if (hasExpired) { + states.remove(key); + shouldReport = true; + } else { + shouldReport = state.getStrategy().onReportingPeriodEnd(state); + } + + if (shouldReport) { + log.debug("[{}] Going to report last activity event for key: [{}].", name, key); + reporter.report(key, lastRecordedTime, state, new ActivityReportCallback<>() { + @Override + public void onSuccess(Key key, long newLastReportedTime) { + updateLastReportedTime(key, newLastReportedTime); + } + + @Override + public void onFailure(Key key, Throwable t) { + log.debug("[{}] Failed to report last activity event in a period for key: [{}].", name, key, t); + } + }); + } + } } - protected abstract void doOnReportingPeriodEnd(); + private void updateLastReportedTime(Key key, long newLastReportedTime) { + states.computeIfPresent(key, (__, activityState) -> { + activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); + return activityState; + }); + } @Override public synchronized void destroy() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index 8f006003ef..53228cb1a2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -1,22 +1,22 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * + *

* Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * + *

* NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, * if any. The intellectual and technical concepts contained * herein are proprietary to ThingsBoard, Inc. * and its suppliers and may be covered by U.S. and Foreign Patents, * patents in process, and are protected by trade secret or copyright law. - * + *

* Dissemination of this information or reproduction of this material is strictly forbidden * unless prior written permission is obtained from COMPANY. - * + *

* Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, * managers or contractors who have executed Confidentiality and Non-disclosure agreements * explicitly covering such access. - * + *

* The copyright notice above does not evidence any actual or intended publication * or disclosure of this source code, which includes * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. @@ -32,11 +32,11 @@ package org.thingsboard.server.common.transport.activity; import java.util.function.Supplier; -public interface ActivityManager { +public interface ActivityManager { - void init(String name, long reportingPeriodMillis, ActivityStateReporter reporter); + void init(String name, long reportingPeriodMillis, ActivityStateReporter> reporter); - void onActivity(Key key, Supplier newStateSupplier); + void onActivity(Key key, Supplier> newStateSupplier); void destroy(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java index 09285f9192..f0f607ddd3 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java @@ -31,11 +31,14 @@ package org.thingsboard.server.common.transport.activity; import lombok.Data; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; @Data -public class ActivityState { +public class ActivityState { private volatile long lastRecordedTime; private volatile long lastReportedTime; + private volatile ActivityStrategy strategy; + private volatile Metadata metadata; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java new file mode 100644 index 0000000000..58c8c6d165 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java @@ -0,0 +1,11 @@ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.thingsboard.server.common.transport.activity.ActivityState; + +public interface ActivityStrategy { + + boolean onActivity(ActivityState state); + + boolean onReportingPeriodEnd(ActivityState state); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java new file mode 100644 index 0000000000..b351d4a59a --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java @@ -0,0 +1,20 @@ +package org.thingsboard.server.common.transport.activity.strategy; + +public enum ActivityStrategyType { + + FIRST(new FirstEventActivityStrategy()), + LAST(new LastEventActivityStrategy()), + FIRST_AND_LAST(new FirstAndLastEventActivityStrategy()), + ALL(new AllEventsActivityStrategy()); + + private final ActivityStrategy strategy; + + ActivityStrategyType(ActivityStrategy strategy) { + this.strategy = strategy; + } + + public ActivityStrategy getStrategy() { + return strategy; + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java new file mode 100644 index 0000000000..add491c3fa --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.thingsboard.server.common.transport.activity.ActivityState; + +public class AllEventsActivityStrategy implements ActivityStrategy { + + @Override + public boolean onActivity(ActivityState state) { + return state.getLastReportedTime() < state.getLastReportedTime(); + } + + @Override + public boolean onReportingPeriodEnd(ActivityState state) { + return state.getLastReportedTime() < state.getLastReportedTime(); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java new file mode 100644 index 0000000000..ecfbaff005 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java @@ -0,0 +1,25 @@ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.thingsboard.server.common.transport.activity.ActivityState; + +public class FirstAndLastEventActivityStrategy implements ActivityStrategy { + + private volatile long firstEventTs; + + @Override + public boolean onActivity(ActivityState state) { + long lastRecordedTime = state.getLastRecordedTime(); + if (firstEventTs == 0L) { + firstEventTs = lastRecordedTime; + return state.getLastReportedTime() < firstEventTs; + } + return false; + } + + @Override + public boolean onReportingPeriodEnd(ActivityState state) { + firstEventTs = 0L; + return state.getLastReportedTime() < state.getLastRecordedTime(); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java new file mode 100644 index 0000000000..2b787c3251 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java @@ -0,0 +1,25 @@ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.thingsboard.server.common.transport.activity.ActivityState; + +public class FirstEventActivityStrategy implements ActivityStrategy { + + private volatile long firstEventTs; + + @Override + public boolean onActivity(ActivityState state) { + long lastRecordedTime = state.getLastRecordedTime(); + if (firstEventTs == 0L) { + firstEventTs = lastRecordedTime; + return state.getLastReportedTime() < firstEventTs; + } + return false; + } + + @Override + public boolean onReportingPeriodEnd(ActivityState state) { + firstEventTs = 0L; + return false; + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java new file mode 100644 index 0000000000..57260ed5ca --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.thingsboard.server.common.transport.activity.ActivityState; + +public class LastEventActivityStrategy implements ActivityStrategy { + + @Override + public boolean onActivity(ActivityState state) { + return false; + } + + @Override + public boolean onReportingPeriodEnd(ActivityState state) { + return state.getLastReportedTime() < state.getLastRecordedTime(); + } + +} From 7431233f5d519b814e13c6282a8105d47dfddc3f Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 15 Dec 2023 09:02:06 +0200 Subject: [PATCH 17/31] [WIP] Reimplemented activity management --- .../integration/IntegrationActivityKey.java | 14 +- .../activity/AbstractActivityManager.java | 120 ++++++----- .../transport/activity/ActivityManager.java | 18 +- .../activity/strategy/ActivityStrategy.java | 36 +++- .../strategy/ActivityStrategyFactory.java | 58 +++++ .../strategy/ActivityStrategyType.java | 20 -- .../strategy/AllEventsActivityStrategy.java | 40 +++- .../FirstAndLastEventActivityStrategy.java | 49 ++++- .../strategy/FirstEventActivityStrategy.java | 50 ++++- .../strategy/LastEventActivityStrategy.java | 38 +++- .../service/DefaultTransportService.java | 135 ++++++++---- ...tAndLastEventTransportActivityManager.java | 199 ----------------- ...nlyFirstEventTransportActivityManager.java | 200 ------------------ ...OnlyLastEventTransportActivityManager.java | 149 ------------- 14 files changed, 420 insertions(+), 706 deletions(-) rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java => application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java (83%) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastEventTransportActivityManager.java delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyFirstEventTransportActivityManager.java delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyLastEventTransportActivityManager.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java b/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java similarity index 83% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java rename to application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java index 1092c569d2..d830d382cb 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityStateReporter.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java @@ -28,11 +28,17 @@ * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. */ -package org.thingsboard.server.common.transport.activity; +package org.thingsboard.server.service.integration; -@FunctionalInterface -public interface ActivityStateReporter { +import lombok.NonNull; +import lombok.Value; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; - void report(Key key, long timeToReport, State activityState, ActivityReportCallback callback); +@Value +public class IntegrationActivityKey { + + @NonNull TenantId tenantId; + @NonNull DeviceId deviceId; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 065a0106f3..1c5da90bbd 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -1,22 +1,22 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - *

+ * * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - *

+ * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, * if any. The intellectual and technical concepts contained * herein are proprietary to ThingsBoard, Inc. * and its suppliers and may be covered by U.S. and Foreign Patents, * patents in process, and are protected by trade secret or copyright law. - *

+ * * Dissemination of this information or reproduction of this material is strictly forbidden * unless prior written permission is obtained from COMPANY. - *

+ * * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, * managers or contractors who have executed Confidentiality and Non-disclosure agreements * explicitly covering such access. - *

+ * * The copyright notice above does not evidence any actual or intended publication * or disclosure of this source code, which includes * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. @@ -36,7 +36,6 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; import java.util.Map; -import java.util.Objects; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -44,38 +43,47 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; @Slf4j -public abstract class AbstractActivityManager implements ActivityManager> { +public abstract class AbstractActivityManager implements ActivityManager { private final ConcurrentMap> states = new ConcurrentHashMap<>(); - private String name; + protected String name; private long reportingPeriodMillis; - private ActivityStateReporter> reporter; private ScheduledExecutorService scheduler; private boolean initialized; @Override - public synchronized void init(String name, long reportingPeriodMillis, ActivityStateReporter> reporter) { + public synchronized void init(String name, long reportingPeriodMillis) { if (!initialized) { this.name = StringUtils.notBlankOrDefault(name, "activity-manager"); - log.info("[{}] initializing.", this.name); - this.reporter = Objects.requireNonNull(reporter, "Failed to initialize activity manager: provided activity reporter is null."); + log.info("Activity manager with name [{}] is initializing.", this.name); if (reportingPeriodMillis <= 0) { reportingPeriodMillis = 3000; log.error("[{}] Negative or zero reporting period millisecond was provided. Going to use reporting period value of 3 seconds.", this.name); } this.reportingPeriodMillis = reportingPeriodMillis; - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(this.name)); + scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(this.name)); // TODO: use scheduler component scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodMillis), reportingPeriodMillis, TimeUnit.MILLISECONDS); initialized = true; - log.info("[{}] initialized.", this.name); + log.info("Activity manager with name [{}] is initialized.", this.name); } } + protected abstract ActivityState createNewState(Key key); + + protected abstract ActivityStrategy getStrategy(); + + protected abstract ActivityState updateState(Key key, ActivityState state); + + protected abstract boolean hasExpired(Key key, ActivityState state); + + protected abstract void onStateExpire(Key key, Metadata metadata); + + protected abstract void reportActivity(Key key, Metadata metadata, long timeToReport, ActivityReportCallback callback); + @Override - public void onActivity(Key key, Supplier> newStateSupplier) { + public void onActivity(Key key) { if (!initialized) { log.error("[{}] Failed to process activity event: activity manager is not initialized.", name); return; @@ -84,39 +92,35 @@ public abstract class AbstractActivityManager implements Activity log.error("[{}] Failed to process activity event: provided activity key is null.", name); return; } - if (newStateSupplier == null) { - log.error("[{}] Failed to process activity event: provided new activity state supplier is null.", name); - return; - } log.debug("[{}] Received activity event for key: [{}]", name, key); - doOnActivity(key, newStateSupplier); - } - - private boolean validate(Key key, Supplier> newStateSupplier) { - - } - - protected abstract ActivityStrategy getStrategy(); - private void doOnActivity(Key key, Supplier> newStateSupplier) { long newLastRecordedTime = System.currentTimeMillis(); - var shouldReport = new AtomicBoolean(false); var activityState = states.compute(key, (__, state) -> { if (state == null) { - state = newStateSupplier.get(); + var newState = createNewState(key); + if (newState == null) { + return null; + } + state = newState; state.setStrategy(getStrategy()); } if (state.getLastRecordedTime() < newLastRecordedTime) { state.setLastRecordedTime(newLastRecordedTime); } - shouldReport.set(state.getStrategy().onActivity(state)); + shouldReport.set(state.getStrategy().onActivity()); return state; }); - if (shouldReport.get()) { + if (activityState == null) { + return; + } + + long lastRecordedTime = activityState.getLastRecordedTime(); + long lastReportedTime = activityState.getLastReportedTime(); + if (shouldReport.get() && lastReportedTime < lastRecordedTime) { log.debug("[{}] Going to report first activity event for key: [{}].", name, key); - reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { + reportActivity(key, activityState.getMetadata(), lastRecordedTime, new ActivityReportCallback<>() { @Override public void onSuccess(Key key, long reportedTime) { updateLastReportedTime(key, reportedTime); @@ -130,33 +134,53 @@ public abstract class AbstractActivityManager implements Activity } } + protected long getLastRecordedTime(Key key) { + ActivityState state = states.get(key); + return state == null ? 0L : state.getLastRecordedTime(); + } + private void onReportingPeriodEnd() { log.debug("[{}] Going to end reporting period.", name); for (Map.Entry> entry : states.entrySet()) { var key = entry.getKey(); - var state = entry.getValue(); - long lastRecordedTime = state.getLastRecordedTime(); + var currentState = entry.getValue(); - boolean hasExpired = false; // TODO: implement state expiration + long lastRecordedTime = currentState.getLastRecordedTime(); + long lastReportedTime = currentState.getLastReportedTime(); + var metadata = currentState.getMetadata(); + + boolean hasExpired; boolean shouldReport; - if (hasExpired) { + + var updatedState = updateState(key, currentState); + if (updatedState != null) { + lastRecordedTime = updatedState.getLastRecordedTime(); + lastReportedTime = updatedState.getLastReportedTime(); + metadata = updatedState.getMetadata(); + hasExpired = hasExpired(key, updatedState); + shouldReport = updatedState.getStrategy().onReportingPeriodEnd(); + } else { states.remove(key); + hasExpired = false; shouldReport = true; - } else { - shouldReport = state.getStrategy().onReportingPeriodEnd(state); } - if (shouldReport) { + if (hasExpired) { + states.remove(key); + onStateExpire(key, metadata); + } + + if (shouldReport && lastReportedTime < lastRecordedTime) { log.debug("[{}] Going to report last activity event for key: [{}].", name, key); - reporter.report(key, lastRecordedTime, state, new ActivityReportCallback<>() { + reportActivity(key, metadata, currentState.getLastRecordedTime(), new ActivityReportCallback<>() { @Override - public void onSuccess(Key key, long newLastReportedTime) { - updateLastReportedTime(key, newLastReportedTime); + public void onSuccess(Key key, long reportedTime) { + updateLastReportedTime(key, reportedTime); } @Override public void onFailure(Key key, Throwable t) { - log.debug("[{}] Failed to report last activity event in a period for key: [{}].", name, key, t); + log.debug("[{}] Failed to report last activity event for key: [{}].", name, key, t); } }); } @@ -164,9 +188,9 @@ public abstract class AbstractActivityManager implements Activity } private void updateLastReportedTime(Key key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityState) -> { - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityState; + states.computeIfPresent(key, (__, state) -> { + state.setLastReportedTime(Math.max(state.getLastReportedTime(), newLastReportedTime)); + return state; }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index 53228cb1a2..5e659bef1e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -1,22 +1,22 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - *

+ * * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - *

+ * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, * if any. The intellectual and technical concepts contained * herein are proprietary to ThingsBoard, Inc. * and its suppliers and may be covered by U.S. and Foreign Patents, * patents in process, and are protected by trade secret or copyright law. - *

+ * * Dissemination of this information or reproduction of this material is strictly forbidden * unless prior written permission is obtained from COMPANY. - *

+ * * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, * managers or contractors who have executed Confidentiality and Non-disclosure agreements * explicitly covering such access. - *

+ * * The copyright notice above does not evidence any actual or intended publication * or disclosure of this source code, which includes * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. @@ -30,13 +30,11 @@ */ package org.thingsboard.server.common.transport.activity; -import java.util.function.Supplier; +public interface ActivityManager { -public interface ActivityManager { + void init(String name, long reportingPeriodMillis); - void init(String name, long reportingPeriodMillis, ActivityStateReporter> reporter); - - void onActivity(Key key, Supplier> newStateSupplier); + void onActivity(Key key); void destroy(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java index 58c8c6d165..1bb8dfdcaf 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java @@ -1,11 +1,39 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ package org.thingsboard.server.common.transport.activity.strategy; -import org.thingsboard.server.common.transport.activity.ActivityState; - public interface ActivityStrategy { - boolean onActivity(ActivityState state); + boolean onActivity(); - boolean onReportingPeriodEnd(ActivityState state); + boolean onReportingPeriodEnd(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java new file mode 100644 index 0000000000..f4619e2afd --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java @@ -0,0 +1,58 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity.strategy; + +public final class ActivityStrategyFactory { + + private static final String ALL_EVENTS_STRATEGY_NAME = "ALL"; + private static final String FIRST_EVENT_STRATEGY_NAME = "FIRST"; + private static final String LAST_EVENT_STRATEGY_NAME = "LAST"; + private static final String FIRST_AND_LAST_STRATEGY_NAME = "FIRST_AND_LAST"; + + private ActivityStrategyFactory() { + } + + public static ActivityStrategy createStrategy(String strategyName) { + switch (strategyName) { + case ALL_EVENTS_STRATEGY_NAME: + return new AllEventsActivityStrategy(); + case FIRST_EVENT_STRATEGY_NAME: + return new FirstEventActivityStrategy(); + case LAST_EVENT_STRATEGY_NAME: + return new LastEventActivityStrategy(); + case FIRST_AND_LAST_STRATEGY_NAME: + return new FirstAndLastEventActivityStrategy(); + default: + throw new IllegalArgumentException("Unknown activity strategy name: [" + strategyName + "."); + } + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java deleted file mode 100644 index b351d4a59a..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.thingsboard.server.common.transport.activity.strategy; - -public enum ActivityStrategyType { - - FIRST(new FirstEventActivityStrategy()), - LAST(new LastEventActivityStrategy()), - FIRST_AND_LAST(new FirstAndLastEventActivityStrategy()), - ALL(new AllEventsActivityStrategy()); - - private final ActivityStrategy strategy; - - ActivityStrategyType(ActivityStrategy strategy) { - this.strategy = strategy; - } - - public ActivityStrategy getStrategy() { - return strategy; - } - -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java index add491c3fa..27797c5315 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java @@ -1,17 +1,45 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ package org.thingsboard.server.common.transport.activity.strategy; -import org.thingsboard.server.common.transport.activity.ActivityState; - public class AllEventsActivityStrategy implements ActivityStrategy { @Override - public boolean onActivity(ActivityState state) { - return state.getLastReportedTime() < state.getLastReportedTime(); + public boolean onActivity() { + return true; } @Override - public boolean onReportingPeriodEnd(ActivityState state) { - return state.getLastReportedTime() < state.getLastReportedTime(); + public boolean onReportingPeriodEnd() { + return true; } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java index ecfbaff005..c2605cdeaf 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java @@ -1,25 +1,52 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ package org.thingsboard.server.common.transport.activity.strategy; -import org.thingsboard.server.common.transport.activity.ActivityState; - public class FirstAndLastEventActivityStrategy implements ActivityStrategy { - private volatile long firstEventTs; + private boolean firstEventReceived; @Override - public boolean onActivity(ActivityState state) { - long lastRecordedTime = state.getLastRecordedTime(); - if (firstEventTs == 0L) { - firstEventTs = lastRecordedTime; - return state.getLastReportedTime() < firstEventTs; + public synchronized boolean onActivity() { + if (!firstEventReceived) { + firstEventReceived = true; + return true; } return false; } @Override - public boolean onReportingPeriodEnd(ActivityState state) { - firstEventTs = 0L; - return state.getLastReportedTime() < state.getLastRecordedTime(); + public synchronized boolean onReportingPeriodEnd() { + firstEventReceived = false; + return true; } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java index 2b787c3251..d837591e04 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java @@ -1,24 +1,54 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ package org.thingsboard.server.common.transport.activity.strategy; -import org.thingsboard.server.common.transport.activity.ActivityState; - public class FirstEventActivityStrategy implements ActivityStrategy { - private volatile long firstEventTs; + private boolean firstEventReceived; @Override - public boolean onActivity(ActivityState state) { - long lastRecordedTime = state.getLastRecordedTime(); - if (firstEventTs == 0L) { - firstEventTs = lastRecordedTime; - return state.getLastReportedTime() < firstEventTs; + public synchronized boolean onActivity() { + if (!firstEventReceived) { + firstEventReceived = true; + return true; } return false; } @Override - public boolean onReportingPeriodEnd(ActivityState state) { - firstEventTs = 0L; + public synchronized boolean onReportingPeriodEnd() { + if (!firstEventReceived) { + return true; + } + firstEventReceived = false; return false; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java index 57260ed5ca..e22263096e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java @@ -1,17 +1,45 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ package org.thingsboard.server.common.transport.activity.strategy; -import org.thingsboard.server.common.transport.activity.ActivityState; - public class LastEventActivityStrategy implements ActivityStrategy { @Override - public boolean onActivity(ActivityState state) { + public boolean onActivity() { return false; } @Override - public boolean onReportingPeriodEnd(ActivityState state) { - return state.getLastReportedTime() < state.getLastRecordedTime(); + public boolean onReportingPeriodEnd() { + return true; } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index a86bdda312..5646e35cec 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -23,7 +23,6 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; @@ -72,8 +71,11 @@ import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportTenantProfileCache; -import org.thingsboard.server.common.transport.activity.ActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityStateReporter; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyFactory; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; @@ -132,7 +134,7 @@ import java.util.stream.Collectors; @Slf4j @Service @TbTransportComponent -public class DefaultTransportService implements TransportService { +public class DefaultTransportService extends AbstractActivityManager implements TransportService { public static final String OVERWRITE_ACTIVITY_TIME = "overwriteActivityTime"; public static final String SESSION_EXPIRED_MESSAGE = "Session has expired due to last activity time!"; @@ -156,6 +158,8 @@ public class DefaultTransportService implements TransportService { private long sessionInactivityTimeout; @Value("${transport.sessions.report_timeout}") private long sessionReportTimeout; + @Value("${transport.activity.reporting_strategy:LAST}") + private String reportingStrategyName; @Value("${transport.client_side_rpc.timeout:60000}") private long clientSideRpcTimeout; @Value("${queue.transport.poll_interval}") @@ -187,7 +191,6 @@ public class DefaultTransportService implements TransportService { private final TransportResourceCache transportResourceCache; private final NotificationRuleProcessor notificationRuleProcessor; private final EntityLimitsCache entityLimitsCache; - private ActivityManager activityManager; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -235,13 +238,9 @@ public class DefaultTransportService implements TransportService { this.entityLimitsCache = entityLimitsCache; } - @Autowired - public void setActivityManager(ActivityManager activityManager) { - this.activityManager = activityManager; - } - @PostConstruct public void init() { + super.init(ACTIVITY_MANAGER_NAME, sessionReportTimeout); this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer"); this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); @@ -256,31 +255,8 @@ public class DefaultTransportService implements TransportService { transportNotificationsConsumer.subscribe(Collections.singleton(tpi)); transportApiRequestTemplate.init(); mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("transport-consumer")); - activityManager.init(ACTIVITY_MANAGER_NAME, sessionReportTimeout, activityReporter); } - private final ActivityStateReporter activityReporter = (sessionId, timeToReport, state, reportCallback) -> { - log.debug("[{}] Reporting activity state for session with id: [{}]. Time to report: [{}].", ACTIVITY_MANAGER_NAME, sessionId, timeToReport); - SessionMetaData sessionMetaData = sessions.get(sessionId); - TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToAttributes()) - .setRpcSubscription(sessionMetaData != null && sessionMetaData.isSubscribedToRPC()) - .setLastActivityTime(timeToReport) - .build(); - process(state.getSessionInfoProto(), subscriptionInfo, new TransportServiceCallback<>() { - @Override - public void onSuccess(Void msgAcknowledged) { - reportCallback.onSuccess(sessionId, timeToReport); - - } - - @Override - public void onError(Throwable e) { - reportCallback.onFailure(sessionId, e); - } - }); - }; - @AfterStartUp(order = AfterStartUp.TRANSPORT_SERVICE) public void start() { mainConsumerExecutor.execute(() -> { @@ -319,6 +295,7 @@ public class DefaultTransportService implements TransportService { @PreDestroy public void destroy() { stopped = true; + super.destroy(); if (transportNotificationsConsumer != null) { transportNotificationsConsumer.unsubscribe(); @@ -332,9 +309,6 @@ public class DefaultTransportService implements TransportService { if (transportApiRequestTemplate != null) { transportApiRequestTemplate.stop(); } - if (activityManager != null) { - activityManager.destroy(); - } } @Override @@ -814,10 +788,91 @@ public class DefaultTransportService implements TransportService { } private void reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { - activityManager.onActivity(toSessionId(sessionInfo), () -> { - var activityState = new TransportActivityState(); - activityState.setSessionInfoProto(sessionInfo); - return activityState; + onActivity(toSessionId(sessionInfo)); + } + + @Override + protected ActivityState createNewState(UUID sessionId) { + SessionMetaData session = sessions.get(sessionId); + if (session == null) { + return null; + } + ActivityState state = new ActivityState<>(); + state.setMetadata(session.getSessionInfo()); + return state; + } + + @Override + protected ActivityStrategy getStrategy() { + return ActivityStrategyFactory.createStrategy(reportingStrategyName); + } + + @Override + protected ActivityState updateState(UUID sessionId, ActivityState state) { + SessionMetaData session = sessions.get(sessionId); + if (session == null) { + return null; + } + + state.setMetadata(session.getSessionInfo()); + var sessionInfo = state.getMetadata(); + + if (sessionInfo.getGwSessionIdMSB() == 0L || sessionInfo.getCustomerIdLSB() == 0L) { + return state; + } + + var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); + SessionMetaData gwSession = sessions.get(gwSessionId); + if (gwSession == null || !gwSession.isOverwriteActivityTime()) { + return state; + } + + long lastRecordedTime = state.getLastRecordedTime(); + long gwLastRecordedTime = getLastRecordedTime(gwSessionId); + log.debug("[{}] Session with id: [{}] has gateway session with id: [{}] with overwrite activity time enabled. " + + "Updating last activity time. Session last recorded time: [{}], gateway session last recorded time: [{}].", + name, sessionId, gwSessionId, lastRecordedTime, gwLastRecordedTime); + state.setLastRecordedTime(Math.max(lastRecordedTime, gwLastRecordedTime)); + return state; + } + + @Override + protected boolean hasExpired(UUID uuid, ActivityState state) { + return (System.currentTimeMillis() - sessionInactivityTimeout) < state.getLastReportedTime(); + } + + @Override + protected void onStateExpire(UUID sessionId, TransportProtos.SessionInfoProto sessionInfo) { + log.debug("[{}] Session with id: [{}] has expired due to last activity time.", name, sessionId); + SessionMetaData expiredSession = sessions.remove(sessionId); + if (expiredSession != null) { + deregisterSession(sessionInfo); + process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); + expiredSession.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); + } + } + + @Override + protected void reportActivity(UUID sessionId, TransportProtos.SessionInfoProto currentSessionInfo, long timeToReport, ActivityReportCallback callback) { + log.debug("[{}] Reporting activity state for session with id: [{}]. Time to report: [{}].", name, sessionId, timeToReport); + SessionMetaData session = sessions.get(sessionId); + TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(session != null && session.isSubscribedToAttributes()) + .setRpcSubscription(session != null && session.isSubscribedToRPC()) + .setLastActivityTime(timeToReport) + .build(); + TransportProtos.SessionInfoProto sessionInfo = session != null ? session.getSessionInfo() : currentSessionInfo; + process(sessionInfo, subscriptionInfo, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + callback.onSuccess(sessionId, timeToReport); + + } + + @Override + public void onError(Throwable e) { + callback.onFailure(sessionId, e); + } }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastEventTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastEventTransportActivityManager.java deleted file mode 100644 index 9c582b2587..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/FirstAndLastEventTransportActivityManager.java +++ /dev/null @@ -1,199 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.common.transport.service.activity; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.util.Pair; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.service.SessionMetaData; -import org.thingsboard.server.common.transport.service.TransportActivityState; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.TbTransportComponent; - -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; -import java.util.function.Supplier; - -import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; -import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EXPIRED_NOTIFICATION_PROTO; - -@Slf4j -@Component -@TbTransportComponent -@ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first-and-last") -public class FirstAndLastEventTransportActivityManager extends AbstractActivityManager { - - private final ConcurrentMap states = new ConcurrentHashMap<>(); - - @Data - private static class ActivityStateWrapper { - - volatile TransportActivityState state; - volatile boolean alreadyBeenReported; - - } - - @Value("${transport.sessions.inactivity_timeout}") - private long sessionInactivityTimeout; - - @Autowired - private TransportService transportService; - - @Override - protected void doOnActivity(UUID sessionId, Supplier newStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - SettableFuture> reportCompletedFuture = SettableFuture.create(); - states.compute(sessionId, (key, activityStateWrapper) -> { - if (activityStateWrapper == null) { - activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(newStateSupplier.get()); - } - var activityState = activityStateWrapper.getState(); - if (activityState.getLastRecordedTime() < newLastRecordedTime) { - activityState.setLastRecordedTime(newLastRecordedTime); - } - if (activityStateWrapper.isAlreadyBeenReported()) { - return activityStateWrapper; - } - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - log.debug("[{}] Going to report first activity event for session with id: [{}]", name, sessionId); - reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(UUID key, long reportedTime) { - reportCompletedFuture.set(Pair.of(key, reportedTime)); - } - - @Override - public void onFailure(UUID key, Throwable t) { - reportCompletedFuture.setException(t); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(true); - return activityStateWrapper; - }); - Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { - @Override - public void onSuccess(Pair reportResult) { - updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); - } - - @Override - public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event for session with id: [{}]", name, sessionId); - } - }, MoreExecutors.directExecutor()); - } - - @Override - protected void doOnReportingPeriodEnd() { - Set statesToRemove = new HashSet<>(); - for (Map.Entry entry : states.entrySet()) { - var sessionId = entry.getKey(); - var activityStateWrapper = entry.getValue(); - var activityState = activityStateWrapper.getState(); - - SessionMetaData sessionMetaData = transportService.getSession(sessionId); - if (sessionMetaData != null) { - activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); - } else { - log.debug("[{}] Session with id: [{}] is not present. Marking it's activity state for removal.", name, sessionId); - statesToRemove.add(sessionId); - } - - long lastActivityTime = activityState.getLastRecordedTime(); - TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); - - if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { - var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); - SessionMetaData gwSessionMetaData = transportService.getSession(gwSessionId); - if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { - ActivityStateWrapper gwActivityStateWrapper = states.get(gwSessionId); - if (gwActivityStateWrapper != null) { - log.debug("[{}] Session with id: [{}] has gateway session with id: [{}] with overwrite activity time enabled. Updating last activity time.", name, sessionId, gwSessionId); - lastActivityTime = Math.max(gwActivityStateWrapper.getState().getLastRecordedTime(), lastActivityTime); - } - } - } - - long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; - boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; - if (hasExpired) { - log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Marking it's activity state for removal.", name, sessionId, lastActivityTime); - transportService.deregisterSession(sessionInfo); - statesToRemove.add(sessionId); - transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } - if (activityState.getLastReportedTime() < lastActivityTime) { - log.debug("[{}] Going to report last activity event for session with id: [{}].", name, sessionId); - reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(UUID key, long reportedTime) { - updateLastReportedTime(key, reportedTime); - } - - @Override - public void onFailure(UUID key, Throwable t) { - log.debug("[{}] Failed to report last activity event for session with id: [{}].", name, sessionId); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(false); - } - statesToRemove.forEach(states::remove); - } - - private void updateLastReportedTime(UUID key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityStateWrapper) -> { - var activityState = activityStateWrapper.getState(); - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityStateWrapper; - }); - } - -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyFirstEventTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyFirstEventTransportActivityManager.java deleted file mode 100644 index 19a6314450..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyFirstEventTransportActivityManager.java +++ /dev/null @@ -1,200 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.common.transport.service.activity; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.util.Pair; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.service.SessionMetaData; -import org.thingsboard.server.common.transport.service.TransportActivityState; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.TbTransportComponent; - -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; -import java.util.function.Supplier; - -import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; -import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EXPIRED_NOTIFICATION_PROTO; - -@Slf4j -@Component -@TbTransportComponent -@ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "first") -public class OnlyFirstEventTransportActivityManager extends AbstractActivityManager { - - private final ConcurrentMap states = new ConcurrentHashMap<>(); - - @Data - private static class ActivityStateWrapper { - - volatile TransportActivityState state; - volatile boolean alreadyBeenReported; - - } - - @Value("${transport.sessions.inactivity_timeout}") - private long sessionInactivityTimeout; - - @Autowired - private TransportService transportService; - - @Override - protected void doOnActivity(UUID sessionId, Supplier newStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - SettableFuture> reportCompletedFuture = SettableFuture.create(); - states.compute(sessionId, (key, activityStateWrapper) -> { - if (activityStateWrapper == null) { - activityStateWrapper = new ActivityStateWrapper(); - activityStateWrapper.setState(newStateSupplier.get()); - } - var activityState = activityStateWrapper.getState(); - if (activityState.getLastRecordedTime() < newLastRecordedTime) { - activityState.setLastRecordedTime(newLastRecordedTime); - } - if (activityStateWrapper.isAlreadyBeenReported()) { - return activityStateWrapper; - } - if (activityState.getLastReportedTime() < activityState.getLastRecordedTime()) { - log.debug("[{}] Going to report first activity event for session with id: [{}]", name, sessionId); - reporter.report(key, activityState.getLastRecordedTime(), activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(UUID key, long reportedTime) { - reportCompletedFuture.set(Pair.of(key, reportedTime)); - } - - @Override - public void onFailure(UUID key, Throwable t) { - reportCompletedFuture.setException(t); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(true); - return activityStateWrapper; - }); - Futures.addCallback(reportCompletedFuture, new FutureCallback<>() { - @Override - public void onSuccess(Pair reportResult) { - updateLastReportedTime(reportResult.getFirst(), reportResult.getSecond()); - } - - @Override - public void onFailure(@NonNull Throwable t) { - log.debug("[{}] Failed to report first activity event for session with id: [{}]", name, sessionId); - } - }, MoreExecutors.directExecutor()); - } - - @Override - protected void doOnReportingPeriodEnd() { - Set statesToRemove = new HashSet<>(); - for (Map.Entry entry : states.entrySet()) { - var sessionId = entry.getKey(); - var activityStateWrapper = entry.getValue(); - var activityState = activityStateWrapper.getState(); - - SessionMetaData sessionMetaData = transportService.getSession(sessionId); - if (sessionMetaData != null) { - activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); - } else { - log.debug("[{}] Session with id: [{}] is not present. Marking it's activity state for removal.", name, sessionId); - statesToRemove.add(sessionId); - } - - long lastActivityTime = activityState.getLastRecordedTime(); - TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); - - if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { - var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); - SessionMetaData gwSessionMetaData = transportService.getSession(gwSessionId); - if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { - ActivityStateWrapper gwActivityStateWrapper = states.get(gwSessionId); - if (gwActivityStateWrapper != null) { - log.debug("[{}] Session with id: [{}] has gateway session with id: [{}] with overwrite activity time enabled. Updating last activity time.", name, sessionId, gwSessionId); - lastActivityTime = Math.max(gwActivityStateWrapper.getState().getLastRecordedTime(), lastActivityTime); - } - } - } - - long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; - boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; - if (hasExpired) { - log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Marking it's activity state for removal.", name, sessionId, lastActivityTime); - transportService.deregisterSession(sessionInfo); - statesToRemove.add(sessionId); - transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } - boolean shouldReportLeftoverEvents = sessionMetaData == null || hasExpired; - if (shouldReportLeftoverEvents && activityState.getLastReportedTime() < lastActivityTime) { - log.debug("[{}] Going to report leftover activity event for session with id: [{}].", name, sessionId); - reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(UUID key, long reportedTime) { - updateLastReportedTime(key, reportedTime); - } - - @Override - public void onFailure(UUID key, Throwable t) { - log.debug("[{}] Failed to report leftover activity event for session with id: [{}].", name, sessionId); - } - }); - } - activityStateWrapper.setAlreadyBeenReported(false); - } - statesToRemove.forEach(states::remove); - } - - private void updateLastReportedTime(UUID key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityStateWrapper) -> { - var activityState = activityStateWrapper.getState(); - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityStateWrapper; - }); - } - -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyLastEventTransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyLastEventTransportActivityManager.java deleted file mode 100644 index 977930677f..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/activity/OnlyLastEventTransportActivityManager.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.common.transport.service.activity; - -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.common.transport.TransportService; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.service.SessionMetaData; -import org.thingsboard.server.common.transport.service.TransportActivityState; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.TbTransportComponent; - -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; -import java.util.function.Supplier; - -import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; -import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EXPIRED_NOTIFICATION_PROTO; - -@Slf4j -@Component -@TbTransportComponent -@ConditionalOnProperty(prefix = "transport.activity", value = "reporting_strategy", havingValue = "last") -public class OnlyLastEventTransportActivityManager extends AbstractActivityManager { - - private final ConcurrentMap states = new ConcurrentHashMap<>(); - - @Value("${transport.sessions.inactivity_timeout}") - private long sessionInactivityTimeout; - - @Autowired - private TransportService transportService; - - @Override - protected void doOnActivity(UUID sessionId, Supplier newStateSupplier) { - long newLastRecordedTime = System.currentTimeMillis(); - states.compute(sessionId, (__, activityState) -> { - if (activityState == null) { - activityState = newStateSupplier.get(); - } - if (activityState.getLastRecordedTime() < newLastRecordedTime) { - activityState.setLastRecordedTime(newLastRecordedTime); - } - return activityState; - }); - } - - @Override - protected void doOnReportingPeriodEnd() { - Set statesToRemove = new HashSet<>(); - for (Map.Entry entry : states.entrySet()) { - var sessionId = entry.getKey(); - var activityState = entry.getValue(); - - SessionMetaData sessionMetaData = transportService.getSession(sessionId); - if (sessionMetaData != null) { - activityState.setSessionInfoProto(sessionMetaData.getSessionInfo()); - } else { - log.debug("[{}] Session with id: [{}] is not present. Marking it's activity state for removal.", name, sessionId); - statesToRemove.add(sessionId); - } - - long lastActivityTime = activityState.getLastRecordedTime(); - TransportProtos.SessionInfoProto sessionInfo = activityState.getSessionInfoProto(); - - if (sessionInfo.getGwSessionIdMSB() != 0 && sessionInfo.getGwSessionIdLSB() != 0) { - var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); - SessionMetaData gwSessionMetaData = transportService.getSession(gwSessionId); - if (gwSessionMetaData != null && gwSessionMetaData.isOverwriteActivityTime()) { - TransportActivityState gwActivityState = states.get(gwSessionId); - if (gwActivityState != null) { - log.debug("[{}] Session with id: [{}] has gateway session with id: [{}] with overwrite activity time enabled. Updating last activity time.", name, sessionId, gwSessionId); - lastActivityTime = Math.max(gwActivityState.getLastRecordedTime(), lastActivityTime); - } - } - } - - long expirationTime = System.currentTimeMillis() - sessionInactivityTimeout; - boolean hasExpired = sessionMetaData != null && lastActivityTime < expirationTime; - if (hasExpired) { - log.debug("[{}] Session with id: [{}] has expired due to last activity time: [{}]. Marking it's activity state for removal.", name, sessionId, lastActivityTime); - transportService.deregisterSession(sessionInfo); - statesToRemove.add(sessionId); - transportService.process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - sessionMetaData.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } - if (activityState.getLastReportedTime() < lastActivityTime) { - log.debug("[{}] Going to report last activity event for session with id: [{}].", name, sessionId); - reporter.report(sessionId, lastActivityTime, activityState, new ActivityReportCallback<>() { - @Override - public void onSuccess(UUID key, long reportedTime) { - updateLastReportedTime(key, reportedTime); - } - - @Override - public void onFailure(UUID key, Throwable t) { - log.debug("[{}] Failed to report last activity event for session with id: [{}].", name, sessionId); - } - }); - } - } - statesToRemove.forEach(states::remove); - } - - private void updateLastReportedTime(UUID key, long newLastReportedTime) { - states.computeIfPresent(key, (__, activityState) -> { - activityState.setLastReportedTime(Math.max(activityState.getLastReportedTime(), newLastReportedTime)); - return activityState; - }); - } - -} From 315202d9df444d797d73ff23e4c774f6945d5486 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 15 Dec 2023 09:43:49 +0200 Subject: [PATCH 18/31] Fixes after manual testing --- .../common/transport/TransportService.java | 3 -- .../activity/AbstractActivityManager.java | 33 ++++--------------- .../transport/activity/ActivityManager.java | 2 -- .../service/DefaultTransportService.java | 9 +---- 4 files changed, 8 insertions(+), 39 deletions(-) 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 index 067527efec..f51b399a5b 100644 --- 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 @@ -63,7 +63,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce import org.thingsboard.server.gen.transport.TransportProtos.ValidateOrCreateDeviceX509CertRequestMsg; import java.util.List; -import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; @@ -162,7 +161,5 @@ public interface TransportService { boolean hasSession(SessionInfoProto sessionInfo); - SessionMetaData getSession(UUID sessionId); - void createGaugeStats(String openConnections, AtomicInteger connectionsCounter); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 1c5da90bbd..b7d5f5dac5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -31,16 +31,15 @@ package org.thingsboard.server.common.transport.activity; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -48,9 +47,11 @@ import java.util.concurrent.atomic.AtomicBoolean; public abstract class AbstractActivityManager implements ActivityManager { private final ConcurrentMap> states = new ConcurrentHashMap<>(); + + @Autowired + protected SchedulerComponent scheduler; + protected String name; - private long reportingPeriodMillis; - private ScheduledExecutorService scheduler; private boolean initialized; @Override @@ -62,8 +63,6 @@ public abstract class AbstractActivityManager implements Activity reportingPeriodMillis = 3000; log.error("[{}] Negative or zero reporting period millisecond was provided. Going to use reporting period value of 3 seconds.", this.name); } - this.reportingPeriodMillis = reportingPeriodMillis; - scheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(this.name)); // TODO: use scheduler component scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodMillis), reportingPeriodMillis, TimeUnit.MILLISECONDS); initialized = true; log.info("Activity manager with name [{}] is initialized.", this.name); @@ -172,7 +171,7 @@ public abstract class AbstractActivityManager implements Activity if (shouldReport && lastReportedTime < lastRecordedTime) { log.debug("[{}] Going to report last activity event for key: [{}].", name, key); - reportActivity(key, metadata, currentState.getLastRecordedTime(), new ActivityReportCallback<>() { + reportActivity(key, metadata, lastRecordedTime, new ActivityReportCallback<>() { @Override public void onSuccess(Key key, long reportedTime) { updateLastReportedTime(key, reportedTime); @@ -194,22 +193,4 @@ public abstract class AbstractActivityManager implements Activity }); } - @Override - public synchronized void destroy() { - if (initialized) { - initialized = false; - if (scheduler != null) { - scheduler.shutdown(); - try { - if (scheduler.awaitTermination(10L, TimeUnit.SECONDS)) { - scheduler.shutdownNow(); - } - } catch (InterruptedException e) { - scheduler.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - } - } - } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index 5e659bef1e..f19b9e30ca 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -36,6 +36,4 @@ public interface ActivityManager { void onActivity(Key key); - void destroy(); - } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 5646e35cec..4b70638e37 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -186,7 +186,6 @@ public class DefaultTransportService extends AbstractActivityManager state) { - return (System.currentTimeMillis() - sessionInactivityTimeout) < state.getLastReportedTime(); + return (System.currentTimeMillis() - sessionInactivityTimeout) > state.getLastRecordedTime(); } @Override @@ -1351,11 +1349,6 @@ public class DefaultTransportService extends AbstractActivityManager Date: Fri, 15 Dec 2023 10:38:15 +0200 Subject: [PATCH 19/31] Refactor first activity strategy and add tests for all strategies --- .../activity/AbstractActivityManager.java | 1 + .../strategy/FirstEventActivityStrategy.java | 3 - .../strategy/ActivityStrategyFactoryTest.java | 70 +++++++++++++++++++ .../AllEventsActivityStrategyTest.java | 57 +++++++++++++++ ...FirstAndLastEventActivityStrategyTest.java | 68 ++++++++++++++++++ .../FirstEventActivityStrategyTest.java | 67 ++++++++++++++++++ .../LastEventActivityStrategyTest.java | 58 +++++++++++++++ 7 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index b7d5f5dac5..1090c7c341 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -167,6 +167,7 @@ public abstract class AbstractActivityManager implements Activity if (hasExpired) { states.remove(key); onStateExpire(key, metadata); + shouldReport = true; } if (shouldReport && lastReportedTime < lastRecordedTime) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java index d837591e04..d61c7a68aa 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java @@ -45,9 +45,6 @@ public class FirstEventActivityStrategy implements ActivityStrategy { @Override public synchronized boolean onReportingPeriodEnd() { - if (!firstEventReceived) { - return true; - } firstEventReceived = false; return false; } diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java new file mode 100644 index 0000000000..c6eda7eb25 --- /dev/null +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java @@ -0,0 +1,70 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ActivityStrategyFactoryTest { + + @Test + public void testCreateAllEventsStrategy() { + ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("ALL"); + assertInstanceOf(AllEventsActivityStrategy.class, strategy, "Should return an instance of AllEventsActivityStrategy."); + } + + @Test + public void testCreateFirstEventStrategy() { + ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("FIRST"); + assertInstanceOf(FirstEventActivityStrategy.class, strategy, "Should return an instance of FirstEventActivityStrategy."); + } + + @Test + public void testCreateLastEventStrategy() { + ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("LAST"); + assertInstanceOf(LastEventActivityStrategy.class, strategy, "Should return an instance of LastEventActivityStrategy."); + } + + @Test + public void testCreateFirstAndLastEventStrategy() { + ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("FIRST_AND_LAST"); + assertInstanceOf(FirstAndLastEventActivityStrategy.class, strategy, "Should return an instance of FirstAndLastEventActivityStrategy."); + } + + @Test + public void testCreateUnknownStrategy() { + assertThrows(IllegalArgumentException.class, () -> ActivityStrategyFactory.createStrategy("UNKNOWN"), + "Should throw IllegalArgumentException for unknown strategy names."); + } + +} diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java new file mode 100644 index 0000000000..22cbc10ca2 --- /dev/null +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java @@ -0,0 +1,57 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AllEventsActivityStrategyTest { + + private AllEventsActivityStrategy strategy; + + @BeforeEach + public void setUp() { + strategy = new AllEventsActivityStrategy(); + } + + @Test + public void testOnActivity() { + assertTrue(strategy.onActivity(), "onActivity() should always return true."); + } + + @Test + public void testOnReportingPeriodEnd() { + assertTrue(strategy.onReportingPeriodEnd(), "onReportingPeriodEnd() should always return true."); + } + +} diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java new file mode 100644 index 0000000000..597693f926 --- /dev/null +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java @@ -0,0 +1,68 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FirstAndLastEventActivityStrategyTest { + + private FirstAndLastEventActivityStrategy strategy; + + @BeforeEach + public void setUp() { + strategy = new FirstAndLastEventActivityStrategy(); + } + + @Test + public void testOnActivity_FirstCall() { + assertTrue(strategy.onActivity(), "First call of onActivity() should return true."); + } + + @Test + public void testOnActivity_SubsequentCalls() { + assertTrue(strategy.onActivity(), "First call of onActivity() should return true."); + assertFalse(strategy.onActivity(), "Subsequent calls of onActivity() should return false."); + } + + @Test + public void testOnReportingPeriodEnd() { + assertTrue(strategy.onActivity(), "First call of onActivity() should return true."); + assertTrue(strategy.onReportingPeriodEnd(), "onReportingPeriodEnd() should always return true."); + assertTrue(strategy.onActivity(), "onActivity() should return true after onReportingPeriodEnd() for the next reporting period"); + assertTrue(strategy.onReportingPeriodEnd(), "onReportingPeriodEnd() should always return true."); + } + + +} diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java new file mode 100644 index 0000000000..5d4d270f0f --- /dev/null +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java @@ -0,0 +1,67 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class FirstEventActivityStrategyTest { + + private FirstEventActivityStrategy strategy; + + @BeforeEach + public void setUp() { + strategy = new FirstEventActivityStrategy(); + } + + @Test + public void testOnActivity_FirstCall() { + assertTrue(strategy.onActivity(), "First call of onActivity() should return true."); + } + + @Test + public void testOnActivity_SubsequentCalls() { + assertTrue(strategy.onActivity(), "First call of onActivity() should return true."); + assertFalse(strategy.onActivity(), "Subsequent calls of onActivity() should return false."); + } + + @Test + public void testOnReportingPeriodEnd() { + assertTrue(strategy.onActivity(), "First call of onActivity() should return true."); + assertFalse(strategy.onReportingPeriodEnd(), "onReportingPeriodEnd() should always return false."); + assertTrue(strategy.onActivity(), "onActivity() should return true after onReportingPeriodEnd()."); + assertFalse(strategy.onReportingPeriodEnd(), "onReportingPeriodEnd() should always return false."); + } + +} diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java new file mode 100644 index 0000000000..51da85a9c3 --- /dev/null +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java @@ -0,0 +1,58 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.activity.strategy; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LastEventActivityStrategyTest { + + private LastEventActivityStrategy strategy; + + @BeforeEach + public void setUp() { + strategy = new LastEventActivityStrategy(); + } + + @Test + public void testOnActivity() { + assertFalse(strategy.onActivity(), "onActivity() should always return false."); + } + + @Test + public void testOnReportingPeriodEnd() { + assertTrue(strategy.onReportingPeriodEnd(), "onReportingPeriodEnd() should always return true."); + } + +} From 76fec4672006cb593581e64d84289c6ba818270a Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 15 Dec 2023 12:50:14 +0200 Subject: [PATCH 20/31] Minor refactoring and tests for integration activity manager --- .../IntegrationActivityManagerTest.java | 299 ++++++++++++++++++ .../activity/AbstractActivityManager.java | 12 +- .../service/DefaultTransportService.java | 13 +- 3 files changed, 307 insertions(+), 17 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java diff --git a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java new file mode 100644 index 0000000000..6748bced4d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java @@ -0,0 +1,299 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.service.integration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.AllEventsActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.FirstAndLastEventActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.FirstEventActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.LastEventActivityStrategy; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionService; + +import java.util.UUID; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class IntegrationActivityManagerTest { + + private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("1306648a-9b26-11ee-b9d1-0242ac120002")); + private final DeviceId DEVICE_ID = DeviceId.fromString("1d288a06-9b26-11ee-b9d1-0242ac120002"); + + @Mock + private DefaultPlatformIntegrationService integrationServiceMock; + + @Test + void testReportActivity() { + // GIVEN + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + + TbQueueProducer> tbCoreMsgProducerMock = mock(TbQueueProducer.class); + ReflectionTestUtils.setField(integrationServiceMock, "tbCoreMsgProducer", tbCoreMsgProducerMock); + + PartitionService partitionServiceMock = mock(PartitionService.class); + ReflectionTestUtils.setField(integrationServiceMock, "partitionService", partitionServiceMock); + TopicPartitionInfo tpi = TopicPartitionInfo.builder().build(); + when(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).thenReturn(tpi); + + ActivityReportCallback callbackMock = mock(ActivityReportCallback.class); + + long expectedTime = 123L; + + doCallRealMethod().when(integrationServiceMock).reportActivity(key, null, expectedTime, callbackMock); + + // WHEN + integrationServiceMock.reportActivity(key, null, expectedTime, callbackMock); + + // THEN + verify(partitionServiceMock).resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID); + + ArgumentCaptor> msgCaptor = ArgumentCaptor.forClass(TbProtoQueueMsg.class); + ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(TbQueueCallback.class); + verify(tbCoreMsgProducerMock).send(eq(tpi), msgCaptor.capture(), callbackCaptor.capture()); + + TbProtoQueueMsg queueMsg = msgCaptor.getValue(); + + TransportProtos.DeviceActivityProto expectedDeviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setLastActivityTime(expectedTime) + .build(); + + TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() + .setDeviceActivityMsg(expectedDeviceActivityMsg) + .build(); + + assertThat(queueMsg.getKey()).isEqualTo(DEVICE_ID.getId()); + assertThat(queueMsg.getValue()).isEqualTo(expectedToCoreMsg); + + TbQueueCallback queueCallback = callbackCaptor.getValue(); + + queueCallback.onSuccess(null); + verify(callbackMock).onSuccess(key, expectedTime); + + var throwable = new Throwable(); + queueCallback.onFailure(throwable); + verify(callbackMock).onFailure(key, throwable); + } + + @Test + void givenPostTelemetryMsg_whenProcessingMsg_thenShouldCallOnActivity() { + // GIVEN + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setDeviceName("Test Device") + .setDeviceType("default") + .build(); + TransportProtos.PostTelemetryMsg postTelemetryMsg = TransportProtos.PostTelemetryMsg.getDefaultInstance(); + doCallRealMethod().when(integrationServiceMock).process(sessionInfo, postTelemetryMsg, null); + + // WHEN + integrationServiceMock.process(sessionInfo, postTelemetryMsg, null); + + // THEN + verify(integrationServiceMock).onActivity(key); + } + + @Test + void givenPostAttributesMsg_whenProcessingMsg_thenShouldCallOnActivity() { + // GIVEN + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) + .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) + .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) + .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) + .setDeviceName("Test Device") + .setDeviceType("default") + .build(); + TransportProtos.PostAttributeMsg postAttributeMsg = TransportProtos.PostAttributeMsg.getDefaultInstance(); + doCallRealMethod().when(integrationServiceMock).process(sessionInfo, postAttributeMsg, null); + doNothing().when(integrationServiceMock).sendToRuleEngine(any(), any(), any(), any(), any(), any(), any()); + + // WHEN + integrationServiceMock.process(sessionInfo, postAttributeMsg, null); + + // THEN + verify(integrationServiceMock).onActivity(key); + } + + @ParameterizedTest + @MethodSource("provideTestParamsForCreateNewState") + void givenDifferentReportingStrategies_whenCreatingNewState_thenShouldCreateEmptyStateWithCorrectStrategy( + String reportingStrategyName, Class reportingStrategyClass + ) { + // GIVEN + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + when(integrationServiceMock.createNewState(key)).thenCallRealMethod(); + ReflectionTestUtils.setField(integrationServiceMock, "reportingStrategyName", reportingStrategyName); + + // WHEN + ActivityState newState = integrationServiceMock.createNewState(key); + + // THEN + assertThat(newState).isNotNull(); + assertThat(newState.getLastRecordedTime()).isEqualTo(0L); + assertThat(newState.getLastReportedTime()).isEqualTo(0L); + assertThat(newState.getStrategy()).isInstanceOf(reportingStrategyClass); + } + + private static Stream provideTestParamsForCreateNewState() { + return Stream.of( + Arguments.of("ALL", AllEventsActivityStrategy.class), + Arguments.of("FIRST", FirstEventActivityStrategy.class), + Arguments.of("LAST", LastEventActivityStrategy.class), + Arguments.of("FIRST_AND_LAST", FirstAndLastEventActivityStrategy.class) + ); + } + + @Test + void givenActivityState_whenUpdatingActivityState_thenShouldReturnSameInstanceWithNoChanges() { + // GIVEN + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + + long expectedLastRecordedTime = 123L; + long expectedLastReportedTime = 312L; + ActivityStrategy expectedStrategy = spy(new FirstEventActivityStrategy()); + + ActivityState expectedState = new ActivityState<>(); + expectedState.setLastRecordedTime(expectedLastRecordedTime); + expectedState.setLastReportedTime(expectedLastReportedTime); + expectedState.setStrategy(expectedStrategy); + + when(integrationServiceMock.updateState(key, expectedState)).thenCallRealMethod(); + + // WHEN + ActivityState actualNewState = integrationServiceMock.updateState(key, expectedState); + + // THEN + assertThat(actualNewState).isSameAs(expectedState); + assertThat(actualNewState.getLastRecordedTime()).isEqualTo(expectedLastRecordedTime); + assertThat(actualNewState.getLastReportedTime()).isEqualTo(expectedLastReportedTime); + assertThat(actualNewState.getStrategy()).isSameAs(expectedStrategy); + verifyNoInteractions(expectedStrategy); + assertThat(actualNewState.getMetadata()).isNull(); + } + + @ParameterizedTest + @MethodSource("provideTestParamsForHasExpiredTrue") + public void givenExpiredLastRecordedTime_whenCheckingForExpiry_thenShouldReturnTrue(long currentTimeMillis, long lastRecordedTime, long reportingPeriodMillis) { + // GIVEN + ReflectionTestUtils.setField(integrationServiceMock, "reportingPeriodMillis", reportingPeriodMillis); + + when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(currentTimeMillis); + when(integrationServiceMock.hasExpired(lastRecordedTime)).thenCallRealMethod(); + + // WHEN + boolean hasExpired = integrationServiceMock.hasExpired(lastRecordedTime); + + // THEN + assertThat(hasExpired).isTrue(); + } + + private static Stream provideTestParamsForHasExpiredTrue() { + return Stream.of( + Arguments.of(10L, 0L, 9L), + Arguments.of(10L, 7L, 2L), + Arguments.of(10L, 8L, 1L) + ); + } + + @ParameterizedTest + @MethodSource("provideTestParamsForHasExpiredFalse") + public void givenNotExpiredLastRecordedTime_whenCheckingForExpiry_thenShouldReturnFalse(long currentTimeMillis, long lastRecordedTime, long reportingPeriodMillis) { + // GIVEN + ReflectionTestUtils.setField(integrationServiceMock, "reportingPeriodMillis", reportingPeriodMillis); + + when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(currentTimeMillis); + when(integrationServiceMock.hasExpired(lastRecordedTime)).thenCallRealMethod(); + + // WHEN + boolean hasExpired = integrationServiceMock.hasExpired(lastRecordedTime); + + // THEN + assertThat(hasExpired).isFalse(); + } + + private static Stream provideTestParamsForHasExpiredFalse() { + return Stream.of( + Arguments.of(10L, 9L, 2L), + Arguments.of(10L, 0L, 11L), + Arguments.of(10L, 8L, 3L) + ); + } + + @Test + void givenKeyAndVoidMetadata_whenOnStateExpiryCalled_thenShouldDoNothing() { + // GIVEN + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + doCallRealMethod().when(integrationServiceMock).onStateExpiry(key, null); + + // WHEN-THEN + assertThatNoException().isThrownBy(() -> integrationServiceMock.onStateExpiry(key, null)); + } + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 1090c7c341..b5f4bf8f9e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -33,7 +33,6 @@ package org.thingsboard.server.common.transport.activity; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import java.util.Map; @@ -71,13 +70,11 @@ public abstract class AbstractActivityManager implements Activity protected abstract ActivityState createNewState(Key key); - protected abstract ActivityStrategy getStrategy(); - protected abstract ActivityState updateState(Key key, ActivityState state); - protected abstract boolean hasExpired(Key key, ActivityState state); + protected abstract boolean hasExpired(long lastRecordedTime); - protected abstract void onStateExpire(Key key, Metadata metadata); + protected abstract void onStateExpiry(Key key, Metadata metadata); protected abstract void reportActivity(Key key, Metadata metadata, long timeToReport, ActivityReportCallback callback); @@ -102,7 +99,6 @@ public abstract class AbstractActivityManager implements Activity return null; } state = newState; - state.setStrategy(getStrategy()); } if (state.getLastRecordedTime() < newLastRecordedTime) { state.setLastRecordedTime(newLastRecordedTime); @@ -156,7 +152,7 @@ public abstract class AbstractActivityManager implements Activity lastRecordedTime = updatedState.getLastRecordedTime(); lastReportedTime = updatedState.getLastReportedTime(); metadata = updatedState.getMetadata(); - hasExpired = hasExpired(key, updatedState); + hasExpired = hasExpired(lastRecordedTime); shouldReport = updatedState.getStrategy().onReportingPeriodEnd(); } else { states.remove(key); @@ -166,7 +162,7 @@ public abstract class AbstractActivityManager implements Activity if (hasExpired) { states.remove(key); - onStateExpire(key, metadata); + onStateExpiry(key, metadata); shouldReport = true; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 4b70638e37..9cd41e7a62 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -74,7 +74,6 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyFactory; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; @@ -797,14 +796,10 @@ public class DefaultTransportService extends AbstractActivityManager state = new ActivityState<>(); state.setMetadata(session.getSessionInfo()); + state.setStrategy(ActivityStrategyFactory.createStrategy(reportingStrategyName)); return state; } - @Override - protected ActivityStrategy getStrategy() { - return ActivityStrategyFactory.createStrategy(reportingStrategyName); - } - @Override protected ActivityState updateState(UUID sessionId, ActivityState state) { SessionMetaData session = sessions.get(sessionId); @@ -835,12 +830,12 @@ public class DefaultTransportService extends AbstractActivityManager state) { - return (System.currentTimeMillis() - sessionInactivityTimeout) > state.getLastRecordedTime(); + protected boolean hasExpired(long lastRecordedTime) { + return (System.currentTimeMillis() - sessionInactivityTimeout) > lastRecordedTime; } @Override - protected void onStateExpire(UUID sessionId, TransportProtos.SessionInfoProto sessionInfo) { + protected void onStateExpiry(UUID sessionId, TransportProtos.SessionInfoProto sessionInfo) { log.debug("[{}] Session with id: [{}] has expired due to last activity time.", name, sessionId); SessionMetaData expiredSession = sessions.remove(sessionId); if (expiredSession != null) { From f42ea7972617a9c13f3bf492156d3ead8000c3c1 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 15 Dec 2023 15:43:05 +0200 Subject: [PATCH 21/31] Additional improvements during testing and add tests for transport activity manager --- .../IntegrationActivityManagerTest.java | 58 +- .../coap/client/DefaultCoapClientContext.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 10 +- .../snmp/service/SnmpTransportService.java | 2 +- .../common/transport/TransportService.java | 2 +- .../activity/AbstractActivityManager.java | 3 +- .../transport/activity/ActivityManager.java | 2 + .../strategy/AllEventsActivityStrategy.java | 3 + .../FirstAndLastEventActivityStrategy.java | 3 + .../strategy/FirstEventActivityStrategy.java | 3 + .../strategy/LastEventActivityStrategy.java | 3 + .../service/DefaultTransportService.java | 40 +- .../service/TransportActivityManagerTest.java | 588 ++++++++++++++++++ 13 files changed, 662 insertions(+), 57 deletions(-) create mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java diff --git a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java index 6748bced4d..fdb1710189 100644 --- a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java +++ b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java @@ -81,7 +81,7 @@ public class IntegrationActivityManagerTest { private DefaultPlatformIntegrationService integrationServiceMock; @Test - void testReportActivity() { + void givenKeyAndTimeToReport_whenReportingActivity_thenShouldCorrectlyReportActivity() { // GIVEN var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); @@ -184,58 +184,54 @@ public class IntegrationActivityManagerTest { @ParameterizedTest @MethodSource("provideTestParamsForCreateNewState") void givenDifferentReportingStrategies_whenCreatingNewState_thenShouldCreateEmptyStateWithCorrectStrategy( - String reportingStrategyName, Class reportingStrategyClass + String reportingStrategyName, ActivityStrategy reportingStrategy ) { // GIVEN var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); when(integrationServiceMock.createNewState(key)).thenCallRealMethod(); ReflectionTestUtils.setField(integrationServiceMock, "reportingStrategyName", reportingStrategyName); + ActivityState expectedState = new ActivityState<>(); + expectedState.setStrategy(reportingStrategy); + // WHEN - ActivityState newState = integrationServiceMock.createNewState(key); + ActivityState actualState = integrationServiceMock.createNewState(key); // THEN - assertThat(newState).isNotNull(); - assertThat(newState.getLastRecordedTime()).isEqualTo(0L); - assertThat(newState.getLastReportedTime()).isEqualTo(0L); - assertThat(newState.getStrategy()).isInstanceOf(reportingStrategyClass); + assertThat(actualState).isEqualTo(expectedState); } private static Stream provideTestParamsForCreateNewState() { return Stream.of( - Arguments.of("ALL", AllEventsActivityStrategy.class), - Arguments.of("FIRST", FirstEventActivityStrategy.class), - Arguments.of("LAST", LastEventActivityStrategy.class), - Arguments.of("FIRST_AND_LAST", FirstAndLastEventActivityStrategy.class) + Arguments.of("ALL", new AllEventsActivityStrategy()), + Arguments.of("FIRST", new FirstEventActivityStrategy()), + Arguments.of("LAST", new LastEventActivityStrategy()), + Arguments.of("FIRST_AND_LAST", new FirstAndLastEventActivityStrategy()) ); } @Test - void givenActivityState_whenUpdatingActivityState_thenShouldReturnSameInstanceWithNoChanges() { + void givenActivityState_whenUpdatingActivityState_thenShouldReturnSameInstanceWithNoInteractions() { // GIVEN var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - long expectedLastRecordedTime = 123L; - long expectedLastReportedTime = 312L; - ActivityStrategy expectedStrategy = spy(new FirstEventActivityStrategy()); + ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); - ActivityState expectedState = new ActivityState<>(); - expectedState.setLastRecordedTime(expectedLastRecordedTime); - expectedState.setLastReportedTime(expectedLastReportedTime); - expectedState.setStrategy(expectedStrategy); + ActivityState state = new ActivityState<>(); + state.setLastRecordedTime(123L); + state.setLastReportedTime(312L); + state.setStrategy(strategySpy); + ActivityState stateSpy = spy(state); - when(integrationServiceMock.updateState(key, expectedState)).thenCallRealMethod(); + when(integrationServiceMock.updateState(key, state)).thenCallRealMethod(); // WHEN - ActivityState actualNewState = integrationServiceMock.updateState(key, expectedState); + ActivityState updatedState = integrationServiceMock.updateState(key, state); // THEN - assertThat(actualNewState).isSameAs(expectedState); - assertThat(actualNewState.getLastRecordedTime()).isEqualTo(expectedLastRecordedTime); - assertThat(actualNewState.getLastReportedTime()).isEqualTo(expectedLastReportedTime); - assertThat(actualNewState.getStrategy()).isSameAs(expectedStrategy); - verifyNoInteractions(expectedStrategy); - assertThat(actualNewState.getMetadata()).isNull(); + assertThat(updatedState).isSameAs(state); + verifyNoInteractions(stateSpy); + verifyNoInteractions(strategySpy); } @ParameterizedTest @@ -258,7 +254,8 @@ public class IntegrationActivityManagerTest { return Stream.of( Arguments.of(10L, 0L, 9L), Arguments.of(10L, 7L, 2L), - Arguments.of(10L, 8L, 1L) + Arguments.of(10L, 8L, 1L), + Arguments.of(10000L, 5000L, 3000L) ); } @@ -282,12 +279,13 @@ public class IntegrationActivityManagerTest { return Stream.of( Arguments.of(10L, 9L, 2L), Arguments.of(10L, 0L, 11L), - Arguments.of(10L, 8L, 3L) + Arguments.of(10L, 8L, 3L), + Arguments.of(10000L, 8000L, 3000L) ); } @Test - void givenKeyAndVoidMetadata_whenOnStateExpiryCalled_thenShouldDoNothing() { + void givenKeyAndMetadata_whenOnStateExpiryCalled_thenShouldDoNothing() { // GIVEN var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); doCallRealMethod().when(integrationServiceMock).onStateExpiry(key, null); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 1e68779b70..735ec1d524 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -212,7 +212,7 @@ public class DefaultCoapClientContext implements CoapClientContext { public void reportActivity() { for (TbCoapClientState state : clients.values()) { if (state.getSession() != null) { - transportService.reportActivity(state.getSession()); + transportService.recordActivity(state.getSession()); } } } 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 index 90bddd2edc..8360111a54 100644 --- 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 @@ -322,7 +322,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case PINGREQ: if (checkConnected(ctx, msg)) { ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); - transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + transportService.recordActivity(deviceSessionCtx.getSessionInfo()); } break; case DISCONNECT: @@ -350,7 +350,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { if (gatewaySessionHandler != null) { handleGatewayPublishMsg(ctx, topicName, msgId, mqttMsg); - transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + transportService.recordActivity(deviceSessionCtx.getSessionInfo()); } else { log.error("[gatewaySessionHandler] is null, [{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId); } @@ -526,7 +526,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); attrReqTopicType = TopicType.V2; } else { - transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + transportService.recordActivity(deviceSessionCtx.getSessionInfo()); ack(ctx, msgId, ReturnCode.TOPIC_NAME_INVALID); } } catch (AdaptorException e) { @@ -796,7 +796,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } if (!activityReported) { - transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + transportService.recordActivity(deviceSessionCtx.getSessionInfo()); } ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList)); } @@ -894,7 +894,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } if (!activityReported) { - transportService.reportActivity(deviceSessionCtx.getSessionInfo()); + transportService.recordActivity(deviceSessionCtx.getSessionInfo()); } ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId(), unSubResults)); } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 5d41918cab..3a8811df97 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -416,7 +416,7 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { - transportService.reportActivity(sessionInfo); + transportService.recordActivity(sessionInfo); } 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 index f51b399a5b..d394af3101 100644 --- 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 @@ -145,7 +145,7 @@ public interface TransportService { SessionMetaData registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); - void reportActivity(SessionInfoProto sessionInfo); + void recordActivity(SessionInfoProto sessionInfo); void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index b5f4bf8f9e..07217e57e8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -129,7 +129,8 @@ public abstract class AbstractActivityManager implements Activity } } - protected long getLastRecordedTime(Key key) { + @Override + public long getLastRecordedTime(Key key) { ActivityState state = states.get(key); return state == null ? 0L : state.getLastRecordedTime(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index f19b9e30ca..847e8f4472 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -36,4 +36,6 @@ public interface ActivityManager { void onActivity(Key key); + long getLastRecordedTime(Key key); + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java index 27797c5315..ba7ceab517 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java @@ -30,6 +30,9 @@ */ package org.thingsboard.server.common.transport.activity.strategy; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode public class AllEventsActivityStrategy implements ActivityStrategy { @Override diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java index c2605cdeaf..225d02ccb8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java @@ -30,6 +30,9 @@ */ package org.thingsboard.server.common.transport.activity.strategy; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode public class FirstAndLastEventActivityStrategy implements ActivityStrategy { private boolean firstEventReceived; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java index d61c7a68aa..f4f53de6c0 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java @@ -30,6 +30,9 @@ */ package org.thingsboard.server.common.transport.activity.strategy; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode public class FirstEventActivityStrategy implements ActivityStrategy { private boolean firstEventReceived; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java index e22263096e..f16fbed8d6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java @@ -30,6 +30,9 @@ */ package org.thingsboard.server.common.transport.activity.strategy; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode public class LastEventActivityStrategy implements ActivityStrategy { @Override diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 9cd41e7a62..da0ef3ca99 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -558,7 +558,7 @@ public class DefaultTransportService extends AbstractActivityManager callback) { if (checkLimits(sessionInfo, msg, callback)) { - reportActivityInternal(sessionInfo); + recordActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) .setSessionEvent(msg).build(), callback); } @@ -578,7 +578,7 @@ public class DefaultTransportService extends AbstractActivityManager callback) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount())) { - reportActivityInternal(sessionInfo); + recordActivityInternal(sessionInfo); TenantId tenantId = getTenantId(sessionInfo); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); @@ -639,7 +639,7 @@ public class DefaultTransportService extends AbstractActivityManager callback) { if (checkLimits(sessionInfo, msg, callback)) { - reportActivityInternal(sessionInfo); + recordActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } @@ -652,7 +652,7 @@ public class DefaultTransportService extends AbstractActivityManager(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } @@ -665,7 +665,7 @@ public class DefaultTransportService extends AbstractActivityManager(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } @@ -674,7 +674,7 @@ public class DefaultTransportService extends AbstractActivityManager callback) { if (checkLimits(sessionInfo, msg, callback)) { - reportActivityInternal(sessionInfo); + recordActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } @@ -683,7 +683,7 @@ public class DefaultTransportService extends AbstractActivityManager callback) { if (checkLimits(sessionInfo, msg, callback)) { - reportActivityInternal(sessionInfo); + recordActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setUplinkNotificationMsg(msg).build(), callback); } } @@ -704,7 +704,7 @@ public class DefaultTransportService extends AbstractActivityManager(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, TransportServiceCallback.EMPTY)); @@ -736,7 +736,7 @@ public class DefaultTransportService extends AbstractActivityManager callback) { if (checkLimits(sessionInfo, msg, callback)) { - reportActivityInternal(sessionInfo); + recordActivityInternal(sessionInfo); UUID sessionId = toSessionId(sessionInfo); TenantId tenantId = getTenantId(sessionInfo); DeviceId deviceId = getDeviceId(sessionInfo); @@ -761,7 +761,7 @@ public class DefaultTransportService extends AbstractActivityManager callback) { if (checkLimits(sessionInfo, msg, callback)) { - reportActivityInternal(sessionInfo); + recordActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) .setClaimDevice(msg).build(), callback); } @@ -780,11 +780,11 @@ public class DefaultTransportService extends AbstractActivityManager lastRecordedTime; + return (getCurrentTimeMillis() - sessionInactivityTimeout) > lastRecordedTime; + } + + long getCurrentTimeMillis() { + return System.currentTimeMillis(); } @Override @@ -937,7 +941,7 @@ public class DefaultTransportService extends AbstractActivityManager sessions; + + @BeforeEach + public void setup() { + sessions = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(transportServiceMock, "sessions", sessions); + } + +// @Override +// protected void reportActivity(UUID sessionId, TransportProtos.SessionInfoProto currentSessionInfo, long timeToReport, ActivityReportCallback callback) { +// log.debug("[{}] Reporting activity state for session with id: [{}]. Time to report: [{}].", name, sessionId, timeToReport); +// SessionMetaData session = sessions.get(sessionId); +// TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() +// .setAttributeSubscription(session != null && session.isSubscribedToAttributes()) +// .setRpcSubscription(session != null && session.isSubscribedToRPC()) +// .setLastActivityTime(timeToReport) +// .build(); +// TransportProtos.SessionInfoProto sessionInfo = session != null ? session.getSessionInfo() : currentSessionInfo; +// process(sessionInfo, subscriptionInfo, new TransportServiceCallback<>() { +// @Override +// public void onSuccess(Void msgAcknowledged) { +// callback.onSuccess(sessionId, timeToReport); +// +// } +// +// @Override +// public void onError(Throwable e) { +// callback.onFailure(sessionId, e); +// } +// }); +// } + + @Test + void givenKeyAndTimeToReportAndSessionExists_whenReportingActivity_thenShouldReportActivityWithSubscriptionsAndSessionInfoFromSession() { + // GIVEN + long expectedTime = 123L; + boolean expectedAttributesSubscription = true; + boolean expectedRPCSubscription = true; + TransportProtos.SessionInfoProto expectedSessionInfo = TransportProtos.SessionInfoProto.getDefaultInstance(); + + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + SessionMetaData session = new SessionMetaData(expectedSessionInfo, TransportProtos.SessionType.ASYNC, listenerMock); + session.setSubscribedToAttributes(expectedAttributesSubscription); + session.setSubscribedToRPC(expectedRPCSubscription); + sessions.put(SESSION_ID, session); + + ActivityReportCallback callbackMock = mock(ActivityReportCallback.class); + + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + + doCallRealMethod().when(transportServiceMock).reportActivity(SESSION_ID, sessionInfo, expectedTime, callbackMock); + + // WHEN + transportServiceMock.reportActivity(SESSION_ID, sessionInfo, expectedTime, callbackMock); + + // THEN + ArgumentCaptor sessionInfoCaptor = ArgumentCaptor.forClass(TransportProtos.SessionInfoProto.class); + ArgumentCaptor subscriptionInfoCaptor = ArgumentCaptor.forClass(TransportProtos.SubscriptionInfoProto.class); + ArgumentCaptor> callbackCaptor = ArgumentCaptor.forClass(TransportServiceCallback.class); + + verify(transportServiceMock).process(sessionInfoCaptor.capture(), subscriptionInfoCaptor.capture(), callbackCaptor.capture()); + + assertThat(sessionInfoCaptor.getValue()).isEqualTo(expectedSessionInfo); + + TransportProtos.SubscriptionInfoProto expectedSubscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(expectedAttributesSubscription) + .setRpcSubscription(expectedRPCSubscription) + .setLastActivityTime(expectedTime) + .build(); + assertThat(subscriptionInfoCaptor.getValue()).isEqualTo(expectedSubscriptionInfo); + + TransportServiceCallback queueCallback = callbackCaptor.getValue(); + + queueCallback.onSuccess(null); + verify(callbackMock).onSuccess(SESSION_ID, expectedTime); + + var throwable = new Throwable(); + queueCallback.onError(throwable); + verify(callbackMock).onFailure(SESSION_ID, throwable); + } + + @Test + void givenKeyAndTimeToReportAndSessionDoesNotExist_whenReportingActivity_thenShouldReportActivityWithNoSubscriptionsAndPreviousSessionInfo() { + // GIVEN + long expectedTime = 123L; + boolean expectedAttributesSubscription = false; + boolean expectedRPCSubscription = false; + TransportProtos.SessionInfoProto expectedSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + + ActivityReportCallback callbackMock = mock(ActivityReportCallback.class); + + doCallRealMethod().when(transportServiceMock).reportActivity(SESSION_ID, expectedSessionInfo, expectedTime, callbackMock); + + // WHEN + transportServiceMock.reportActivity(SESSION_ID, expectedSessionInfo, expectedTime, callbackMock); + + // THEN + ArgumentCaptor sessionInfoCaptor = ArgumentCaptor.forClass(TransportProtos.SessionInfoProto.class); + ArgumentCaptor subscriptionInfoCaptor = ArgumentCaptor.forClass(TransportProtos.SubscriptionInfoProto.class); + ArgumentCaptor> callbackCaptor = ArgumentCaptor.forClass(TransportServiceCallback.class); + + verify(transportServiceMock).process(sessionInfoCaptor.capture(), subscriptionInfoCaptor.capture(), callbackCaptor.capture()); + + assertThat(sessionInfoCaptor.getValue()).isEqualTo(expectedSessionInfo); + + TransportProtos.SubscriptionInfoProto expectedSubscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(expectedAttributesSubscription) + .setRpcSubscription(expectedRPCSubscription) + .setLastActivityTime(expectedTime) + .build(); + assertThat(subscriptionInfoCaptor.getValue()).isEqualTo(expectedSubscriptionInfo); + + TransportServiceCallback queueCallback = callbackCaptor.getValue(); + + queueCallback.onSuccess(null); + verify(callbackMock).onSuccess(SESSION_ID, expectedTime); + + var throwable = new Throwable(); + queueCallback.onError(throwable); + verify(callbackMock).onFailure(SESSION_ID, throwable); + } + + @Test + void givenActivityHappened_whenRecordActivity_thenShouldDelegateToOnActivity() { + // GIVEN + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + doCallRealMethod().when(transportServiceMock).recordActivity(sessionInfo); + when(transportServiceMock.toSessionId(sessionInfo)).thenReturn(SESSION_ID); + + // WHEN + transportServiceMock.recordActivity(sessionInfo); + + // THEN + verify(transportServiceMock).onActivity(SESSION_ID); + } + + @ParameterizedTest + @MethodSource("provideTestParamsForCreateNewState") + void givenDifferentReportingStrategies_whenCreatingNewState_thenShouldCreateEmptyStateWithCorrectStrategy( + String reportingStrategyName, ActivityStrategy reportingStrategy + ) { + // GIVEN + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); + + ReflectionTestUtils.setField(transportServiceMock, "reportingStrategyName", reportingStrategyName); + + when(transportServiceMock.createNewState(SESSION_ID)).thenCallRealMethod(); + + ActivityState expectedState = new ActivityState<>(); + expectedState.setStrategy(reportingStrategy); + expectedState.setMetadata(sessionInfo); + + // WHEN + ActivityState actualState = transportServiceMock.createNewState(SESSION_ID); + + // THEN + assertThat(actualState).isEqualTo(expectedState); + } + + private static Stream provideTestParamsForCreateNewState() { + return Stream.of( + Arguments.of("ALL", new AllEventsActivityStrategy()), + Arguments.of("FIRST", new FirstEventActivityStrategy()), + Arguments.of("LAST", new LastEventActivityStrategy()), + Arguments.of("FIRST_AND_LAST", new FirstAndLastEventActivityStrategy()) + ); + } + + @Test + void givenSessionDoesNotExist_whenUpdatingActivityState_thenShouldReturnNull() { + // GIVEN + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + + ActivityState state = new ActivityState<>(); + state.setLastRecordedTime(123L); + state.setLastReportedTime(312L); + state.setMetadata(sessionInfo); + state.setStrategy(new FirstEventActivityStrategy()); + + when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); + + // WHEN + ActivityState updatedState = transportServiceMock.updateState(SESSION_ID, state); + + // THEN + assertThat(updatedState).isNull(); + } + + @Test + void givenNoGwSessionId_whenUpdatingActivityState_thenShouldReturnSameInstanceWithUpdatedSessionInfo() { + // GIVEN + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); + + long lastRecordedTime = 123L; + long lastReportedTime = 312L; + ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); + + ActivityState state = new ActivityState<>(); + state.setLastRecordedTime(lastRecordedTime); + state.setLastReportedTime(lastReportedTime); + state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); + state.setStrategy(strategySpy); + + when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); + + // WHEN + ActivityState updatedState = transportServiceMock.updateState(SESSION_ID, state); + + // THEN + assertThat(updatedState).isSameAs(state); + assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); + assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); + assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); + assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); + verifyNoInteractions(strategySpy); + } + + @Test + void givenHasGwSessionIdButGwSessionIsNotNull_whenUpdatingActivityState_thenShouldReturnSameInstanceWithUpdatedSessionInfo() { + // GIVEN + var gwSessionId = UUID.fromString("19864038-9b48-11ee-b9d1-0242ac120002"); + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .setGwSessionIdMSB(gwSessionId.getMostSignificantBits()) + .setGwSessionIdLSB(gwSessionId.getLeastSignificantBits()) + .build(); + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); + + long lastRecordedTime = 123L; + long lastReportedTime = 312L; + ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); + + ActivityState state = new ActivityState<>(); + state.setLastRecordedTime(lastRecordedTime); + state.setLastReportedTime(lastReportedTime); + state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); + state.setStrategy(strategySpy); + + when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); + + // WHEN + ActivityState updatedState = transportServiceMock.updateState(SESSION_ID, state); + + // THEN + assertThat(updatedState).isSameAs(state); + assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); + assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); + assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); + assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); + verifyNoInteractions(strategySpy); + + verify(transportServiceMock, never()).getLastRecordedTime(gwSessionId); + } + + @Test + void givenHasGwSessionWithoutOverwriteEnabled_whenUpdatingActivityState_thenShouldReturnSameInstanceWithUpdatedSessionInfo() { + // GIVEN + var gwSessionId = UUID.fromString("19864038-9b48-11ee-b9d1-0242ac120002"); + TransportProtos.SessionInfoProto gwSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(gwSessionId.getMostSignificantBits()) + .setSessionIdLSB(gwSessionId.getLeastSignificantBits()) + .build(); + SessionMsgListener gwListenerMock = mock(SessionMsgListener.class); + sessions.put(gwSessionId, new SessionMetaData(gwSessionInfo, TransportProtos.SessionType.ASYNC, gwListenerMock)); + + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .setGwSessionIdMSB(gwSessionId.getMostSignificantBits()) + .setGwSessionIdLSB(gwSessionId.getLeastSignificantBits()) + .build(); + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); + + long lastRecordedTime = 123L; + long lastReportedTime = 312L; + ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); + + ActivityState state = new ActivityState<>(); + state.setLastRecordedTime(lastRecordedTime); + state.setLastReportedTime(lastReportedTime); + state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); + state.setStrategy(strategySpy); + + when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); + + // WHEN + ActivityState updatedState = transportServiceMock.updateState(SESSION_ID, state); + + // THEN + assertThat(updatedState).isSameAs(state); + assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); + assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); + assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); + assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); + verifyNoInteractions(strategySpy); + + verify(transportServiceMock, never()).getLastRecordedTime(gwSessionId); + } + + @Test + void givenHasGwSessionWithOverwriteEnabledAndGwLastRecordedTimeIsGreater_whenUpdatingActivityState_thenShouldReturnSameInstanceWithUpdatedSessionInfoAndLastRecordedTime() { + // GIVEN + var gwSessionId = UUID.fromString("19864038-9b48-11ee-b9d1-0242ac120002"); + TransportProtos.SessionInfoProto gwSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(gwSessionId.getMostSignificantBits()) + .setSessionIdLSB(gwSessionId.getLeastSignificantBits()) + .build(); + SessionMsgListener gwListenerMock = mock(SessionMsgListener.class); + SessionMetaData gwSession = new SessionMetaData(gwSessionInfo, TransportProtos.SessionType.ASYNC, gwListenerMock); + gwSession.setOverwriteActivityTime(true); + sessions.put(gwSessionId, gwSession); + + long gwLastRecordedTime = 500L; + when(transportServiceMock.getLastRecordedTime(gwSessionId)).thenReturn(gwLastRecordedTime); + + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .setGwSessionIdMSB(gwSessionId.getMostSignificantBits()) + .setGwSessionIdLSB(gwSessionId.getLeastSignificantBits()) + .build(); + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); + + long lastRecordedTime = 123L; + long lastReportedTime = 312L; + ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); + + ActivityState state = new ActivityState<>(); + state.setLastRecordedTime(lastRecordedTime); + state.setLastReportedTime(lastReportedTime); + state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); + state.setStrategy(strategySpy); + + when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); + + // WHEN + ActivityState updatedState = transportServiceMock.updateState(SESSION_ID, state); + + // THEN + assertThat(updatedState).isSameAs(state); + assertThat(updatedState.getLastRecordedTime()).isEqualTo(gwLastRecordedTime); + assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); + assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); + assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); + verifyNoInteractions(strategySpy); + } + + @Test + void givenHasGwSessionWithOverwriteEnabledAndGwLastRecordedTimeIsLess_whenUpdatingActivityState_thenShouldReturnSameInstanceWithUpdatedSessionInfoOnly() { + // GIVEN + var gwSessionId = UUID.fromString("19864038-9b48-11ee-b9d1-0242ac120002"); + TransportProtos.SessionInfoProto gwSessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(gwSessionId.getMostSignificantBits()) + .setSessionIdLSB(gwSessionId.getLeastSignificantBits()) + .build(); + SessionMsgListener gwListenerMock = mock(SessionMsgListener.class); + SessionMetaData gwSession = new SessionMetaData(gwSessionInfo, TransportProtos.SessionType.ASYNC, gwListenerMock); + gwSession.setOverwriteActivityTime(true); + sessions.put(gwSessionId, gwSession); + + long gwLastRecordedTime = 100L; + when(transportServiceMock.getLastRecordedTime(gwSessionId)).thenReturn(gwLastRecordedTime); + + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .setGwSessionIdMSB(gwSessionId.getMostSignificantBits()) + .setGwSessionIdLSB(gwSessionId.getLeastSignificantBits()) + .build(); + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); + + long lastRecordedTime = 123L; + long lastReportedTime = 312L; + ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); + + ActivityState state = new ActivityState<>(); + state.setLastRecordedTime(lastRecordedTime); + state.setLastReportedTime(lastReportedTime); + state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); + state.setStrategy(strategySpy); + + when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); + + // WHEN + ActivityState updatedState = transportServiceMock.updateState(SESSION_ID, state); + + // THEN + assertThat(updatedState).isSameAs(state); + assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); + assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); + assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); + assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); + verifyNoInteractions(strategySpy); + } + + @ParameterizedTest + @MethodSource("provideTestParamsForHasExpiredTrue") + public void givenExpiredLastRecordedTime_whenCheckingForExpiry_thenShouldReturnTrue(long currentTimeMillis, long lastRecordedTime, long sessionInactivityTimeout) { + // GIVEN + ReflectionTestUtils.setField(transportServiceMock, "sessionInactivityTimeout", sessionInactivityTimeout); + + when(transportServiceMock.getCurrentTimeMillis()).thenReturn(currentTimeMillis); + when(transportServiceMock.hasExpired(lastRecordedTime)).thenCallRealMethod(); + + // WHEN + boolean hasExpired = transportServiceMock.hasExpired(lastRecordedTime); + + // THEN + assertThat(hasExpired).isTrue(); + } + + private static Stream provideTestParamsForHasExpiredTrue() { + return Stream.of( + Arguments.of(10L, 0L, 9L), + Arguments.of(10L, 7L, 2L), + Arguments.of(10L, 8L, 1L), + Arguments.of(10000L, 5000L, 3000L) + ); + } + + @ParameterizedTest + @MethodSource("provideTestParamsForHasExpiredFalse") + public void givenNotExpiredLastRecordedTime_whenCheckingForExpiry_thenShouldReturnFalse(long currentTimeMillis, long lastRecordedTime, long sessionInactivityTimeout) { + // GIVEN + ReflectionTestUtils.setField(transportServiceMock, "sessionInactivityTimeout", sessionInactivityTimeout); + + when(transportServiceMock.getCurrentTimeMillis()).thenReturn(currentTimeMillis); + when(transportServiceMock.hasExpired(lastRecordedTime)).thenCallRealMethod(); + + // WHEN + boolean hasExpired = transportServiceMock.hasExpired(lastRecordedTime); + + // THEN + assertThat(hasExpired).isFalse(); + } + + private static Stream provideTestParamsForHasExpiredFalse() { + return Stream.of( + Arguments.of(10L, 9L, 2L), + Arguments.of(10L, 0L, 11L), + Arguments.of(10L, 8L, 3L), + Arguments.of(10000L, 8000L, 3000L) + ); + } + + @Test + void givenSessionExists_whenOnStateExpiryCalled_thenShouldPerformExpirationActions() { + // GIVEN + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + SessionMsgListener listenerMock = mock(SessionMsgListener.class); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); + doCallRealMethod().when(transportServiceMock).onStateExpiry(SESSION_ID, sessionInfo); + + // WHEN + transportServiceMock.onStateExpiry(SESSION_ID, sessionInfo); + + // THEN + assertThat(sessions.containsKey(SESSION_ID)).isFalse(); + verify(transportServiceMock).deregisterSession(sessionInfo); + verify(transportServiceMock).process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); + verify(listenerMock).onRemoteSessionCloseCommand(SESSION_ID, SESSION_EXPIRED_NOTIFICATION_PROTO); + } + + @Test + void givenSessionDoesNotExist_whenOnStateExpiryCalled_thenShouldNotPerformExpirationActions() { + // GIVEN + TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() + .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) + .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) + .build(); + doCallRealMethod().when(transportServiceMock).onStateExpiry(SESSION_ID, sessionInfo); + + // WHEN + transportServiceMock.onStateExpiry(SESSION_ID, sessionInfo); + + // THEN + assertThat(sessions.containsKey(SESSION_ID)).isFalse(); + verify(transportServiceMock, never()).deregisterSession(sessionInfo); + verify(transportServiceMock, never()).process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); + } + +} From 07b8140b779bcc1b5edf1e67162e1625b588029d Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 15 Dec 2023 16:13:42 +0200 Subject: [PATCH 22/31] Remove missed comment --- .../service/TransportActivityManagerTest.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java index a97a32a26e..6ad48043ce 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java @@ -82,30 +82,6 @@ public class TransportActivityManagerTest { ReflectionTestUtils.setField(transportServiceMock, "sessions", sessions); } -// @Override -// protected void reportActivity(UUID sessionId, TransportProtos.SessionInfoProto currentSessionInfo, long timeToReport, ActivityReportCallback callback) { -// log.debug("[{}] Reporting activity state for session with id: [{}]. Time to report: [{}].", name, sessionId, timeToReport); -// SessionMetaData session = sessions.get(sessionId); -// TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() -// .setAttributeSubscription(session != null && session.isSubscribedToAttributes()) -// .setRpcSubscription(session != null && session.isSubscribedToRPC()) -// .setLastActivityTime(timeToReport) -// .build(); -// TransportProtos.SessionInfoProto sessionInfo = session != null ? session.getSessionInfo() : currentSessionInfo; -// process(sessionInfo, subscriptionInfo, new TransportServiceCallback<>() { -// @Override -// public void onSuccess(Void msgAcknowledged) { -// callback.onSuccess(sessionId, timeToReport); -// -// } -// -// @Override -// public void onError(Throwable e) { -// callback.onFailure(sessionId, e); -// } -// }); -// } - @Test void givenKeyAndTimeToReportAndSessionExists_whenReportingActivity_thenShouldReportActivityWithSubscriptionsAndSessionInfoFromSession() { // GIVEN From a5c06ec70d6f721012470b1f1ce83c36a5b04c76 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 18 Dec 2023 09:41:45 +0200 Subject: [PATCH 23/31] Refactor: strategy creation, hide implementation details from clients --- .../IntegrationActivityManagerTest.java | 46 +++++------ .../activity/AbstractActivityManager.java | 53 ++++++++----- .../transport/activity/ActivityState.java | 3 - .../strategy/ActivityStrategyType.java} | 36 ++++++--- .../strategy/AllEventsActivityStrategy.java | 12 ++- .../FirstAndLastEventActivityStrategy.java | 2 +- .../strategy/FirstEventActivityStrategy.java | 2 +- .../strategy/LastEventActivityStrategy.java | 12 ++- .../service/DefaultTransportService.java | 11 ++- .../strategy/ActivityStrategyFactoryTest.java | 70 ----------------- .../strategy/ActivityStrategyTypeTest.java} | 39 +++++----- .../AllEventsActivityStrategyTest.java | 2 +- .../LastEventActivityStrategyTest.java | 2 +- .../service/TransportActivityManagerTest.java | 77 ++++--------------- 14 files changed, 148 insertions(+), 219 deletions(-) rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/{service/TransportActivityState.java => activity/strategy/ActivityStrategyType.java} (68%) delete mode 100644 common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java rename common/transport/transport-api/src/{main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java => test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java} (62%) diff --git a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java index fdb1710189..3b85261bac 100644 --- a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java +++ b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -46,10 +47,7 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.AllEventsActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.FirstAndLastEventActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.FirstEventActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.LastEventActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; @@ -181,33 +179,32 @@ public class IntegrationActivityManagerTest { verify(integrationServiceMock).onActivity(key); } - @ParameterizedTest - @MethodSource("provideTestParamsForCreateNewState") - void givenDifferentReportingStrategies_whenCreatingNewState_thenShouldCreateEmptyStateWithCorrectStrategy( - String reportingStrategyName, ActivityStrategy reportingStrategy - ) { + @Test + void givenKey_whenCreatingNewState_thenShouldCorrectlyCreateNewEmptyState() { // GIVEN var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); when(integrationServiceMock.createNewState(key)).thenCallRealMethod(); - ReflectionTestUtils.setField(integrationServiceMock, "reportingStrategyName", reportingStrategyName); - - ActivityState expectedState = new ActivityState<>(); - expectedState.setStrategy(reportingStrategy); // WHEN - ActivityState actualState = integrationServiceMock.createNewState(key); + ActivityState actualNewState = integrationServiceMock.createNewState(key); // THEN - assertThat(actualState).isEqualTo(expectedState); + ActivityState expectedNewState = new ActivityState<>(); + assertThat(actualNewState).isEqualTo(expectedNewState); } - private static Stream provideTestParamsForCreateNewState() { - return Stream.of( - Arguments.of("ALL", new AllEventsActivityStrategy()), - Arguments.of("FIRST", new FirstEventActivityStrategy()), - Arguments.of("LAST", new LastEventActivityStrategy()), - Arguments.of("FIRST_AND_LAST", new FirstAndLastEventActivityStrategy()) - ); + @ParameterizedTest + @EnumSource(ActivityStrategyType.class) + void givenDifferentReportingStrategies_whenGettingStrategy_thenShouldReturnCorrectStrategy(ActivityStrategyType reportingStrategyType) { + // GIVEN + doCallRealMethod().when(integrationServiceMock).getStrategy(); + ReflectionTestUtils.setField(integrationServiceMock, "reportingStrategyType", reportingStrategyType); + + // WHEN + ActivityStrategy actualStrategy = integrationServiceMock.getStrategy(); + + // THEN + assertThat(actualStrategy).isEqualTo(reportingStrategyType.toStrategy()); } @Test @@ -215,12 +212,8 @@ public class IntegrationActivityManagerTest { // GIVEN var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); - ActivityState state = new ActivityState<>(); state.setLastRecordedTime(123L); - state.setLastReportedTime(312L); - state.setStrategy(strategySpy); ActivityState stateSpy = spy(state); when(integrationServiceMock.updateState(key, state)).thenCallRealMethod(); @@ -231,7 +224,6 @@ public class IntegrationActivityManagerTest { // THEN assertThat(updatedState).isSameAs(state); verifyNoInteractions(stateSpy); - verifyNoInteractions(strategySpy); } @ParameterizedTest diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 07217e57e8..2124eb13d2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -30,9 +30,11 @@ */ package org.thingsboard.server.common.transport.activity; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import java.util.Map; @@ -45,7 +47,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @Slf4j public abstract class AbstractActivityManager implements ActivityManager { - private final ConcurrentMap> states = new ConcurrentHashMap<>(); + private final ConcurrentMap states = new ConcurrentHashMap<>(); @Autowired protected SchedulerComponent scheduler; @@ -53,6 +55,15 @@ public abstract class AbstractActivityManager implements Activity protected String name; private boolean initialized; + @Data + private class ActivityStateWrapper { + + private volatile ActivityState state; + private volatile long lastReportedTime; + private volatile ActivityStrategy strategy; + + } + @Override public synchronized void init(String name, long reportingPeriodMillis) { if (!initialized) { @@ -70,6 +81,8 @@ public abstract class AbstractActivityManager implements Activity protected abstract ActivityState createNewState(Key key); + protected abstract ActivityStrategy getStrategy(); + protected abstract ActivityState updateState(Key key, ActivityState state); protected abstract boolean hasExpired(long lastRecordedTime); @@ -92,27 +105,31 @@ public abstract class AbstractActivityManager implements Activity long newLastRecordedTime = System.currentTimeMillis(); var shouldReport = new AtomicBoolean(false); - var activityState = states.compute(key, (__, state) -> { - if (state == null) { + var activityStateWrapper = states.compute(key, (__, stateWrapper) -> { + if (stateWrapper == null) { var newState = createNewState(key); if (newState == null) { return null; } - state = newState; + stateWrapper = new ActivityStateWrapper(); + stateWrapper.setState(newState); + stateWrapper.setStrategy(getStrategy()); } + var state = stateWrapper.getState(); if (state.getLastRecordedTime() < newLastRecordedTime) { state.setLastRecordedTime(newLastRecordedTime); } - shouldReport.set(state.getStrategy().onActivity()); - return state; + shouldReport.set(stateWrapper.getStrategy().onActivity()); + return stateWrapper; }); - if (activityState == null) { + if (activityStateWrapper == null) { return; } + var activityState = activityStateWrapper.getState(); long lastRecordedTime = activityState.getLastRecordedTime(); - long lastReportedTime = activityState.getLastReportedTime(); + long lastReportedTime = activityStateWrapper.getLastReportedTime(); if (shouldReport.get() && lastReportedTime < lastRecordedTime) { log.debug("[{}] Going to report first activity event for key: [{}].", name, key); reportActivity(key, activityState.getMetadata(), lastRecordedTime, new ActivityReportCallback<>() { @@ -131,18 +148,19 @@ public abstract class AbstractActivityManager implements Activity @Override public long getLastRecordedTime(Key key) { - ActivityState state = states.get(key); - return state == null ? 0L : state.getLastRecordedTime(); + ActivityStateWrapper stateWrapper = states.get(key); + return stateWrapper == null ? 0L : stateWrapper.getState().getLastRecordedTime(); } private void onReportingPeriodEnd() { log.debug("[{}] Going to end reporting period.", name); - for (Map.Entry> entry : states.entrySet()) { + for (Map.Entry entry : states.entrySet()) { var key = entry.getKey(); - var currentState = entry.getValue(); + var stateWrapper = entry.getValue(); + var currentState = stateWrapper.getState(); long lastRecordedTime = currentState.getLastRecordedTime(); - long lastReportedTime = currentState.getLastReportedTime(); + long lastReportedTime = stateWrapper.getLastReportedTime(); var metadata = currentState.getMetadata(); boolean hasExpired; @@ -151,10 +169,9 @@ public abstract class AbstractActivityManager implements Activity var updatedState = updateState(key, currentState); if (updatedState != null) { lastRecordedTime = updatedState.getLastRecordedTime(); - lastReportedTime = updatedState.getLastReportedTime(); metadata = updatedState.getMetadata(); hasExpired = hasExpired(lastRecordedTime); - shouldReport = updatedState.getStrategy().onReportingPeriodEnd(); + shouldReport = stateWrapper.getStrategy().onReportingPeriodEnd(); } else { states.remove(key); hasExpired = false; @@ -185,9 +202,9 @@ public abstract class AbstractActivityManager implements Activity } private void updateLastReportedTime(Key key, long newLastReportedTime) { - states.computeIfPresent(key, (__, state) -> { - state.setLastReportedTime(Math.max(state.getLastReportedTime(), newLastReportedTime)); - return state; + states.computeIfPresent(key, (__, stateWrapper) -> { + stateWrapper.setLastReportedTime(Math.max(stateWrapper.getLastReportedTime(), newLastReportedTime)); + return stateWrapper; }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java index f0f607ddd3..b470a52d9d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java @@ -31,14 +31,11 @@ package org.thingsboard.server.common.transport.activity; import lombok.Data; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; @Data public class ActivityState { private volatile long lastRecordedTime; - private volatile long lastReportedTime; - private volatile ActivityStrategy strategy; private volatile Metadata metadata; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityState.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java similarity index 68% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityState.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java index 470699ec5d..bda213936e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityState.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java @@ -28,17 +28,35 @@ * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. */ -package org.thingsboard.server.common.transport.service; +package org.thingsboard.server.common.transport.activity.strategy; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.gen.transport.TransportProtos; +public enum ActivityStrategyType { -@Data -@EqualsAndHashCode(callSuper = true) -public class TransportActivityState extends ActivityState { + ALL { + @Override + public ActivityStrategy toStrategy() { + return AllEventsActivityStrategy.getInstance(); + } + }, + FIRST { + @Override + public ActivityStrategy toStrategy() { + return new FirstEventActivityStrategy(); + } + }, + LAST { + @Override + public ActivityStrategy toStrategy() { + return LastEventActivityStrategy.getInstance(); + } + }, + FIRST_AND_LAST { + @Override + public ActivityStrategy toStrategy() { + return new FirstAndLastEventActivityStrategy(); + } + }; - private volatile TransportProtos.SessionInfoProto sessionInfoProto; + public abstract ActivityStrategy toStrategy(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java index ba7ceab517..6af3469fd5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java @@ -30,10 +30,16 @@ */ package org.thingsboard.server.common.transport.activity.strategy; -import lombok.EqualsAndHashCode; +public final class AllEventsActivityStrategy implements ActivityStrategy { -@EqualsAndHashCode -public class AllEventsActivityStrategy implements ActivityStrategy { + private static final AllEventsActivityStrategy INSTANCE = new AllEventsActivityStrategy(); + + private AllEventsActivityStrategy() { + } + + public static AllEventsActivityStrategy getInstance() { + return INSTANCE; + } @Override public boolean onActivity() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java index 225d02ccb8..71682da1ac 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java @@ -33,7 +33,7 @@ package org.thingsboard.server.common.transport.activity.strategy; import lombok.EqualsAndHashCode; @EqualsAndHashCode -public class FirstAndLastEventActivityStrategy implements ActivityStrategy { +public final class FirstAndLastEventActivityStrategy implements ActivityStrategy { private boolean firstEventReceived; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java index f4f53de6c0..66c2b8a8e2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java @@ -33,7 +33,7 @@ package org.thingsboard.server.common.transport.activity.strategy; import lombok.EqualsAndHashCode; @EqualsAndHashCode -public class FirstEventActivityStrategy implements ActivityStrategy { +public final class FirstEventActivityStrategy implements ActivityStrategy { private boolean firstEventReceived; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java index f16fbed8d6..fd9fe1f3eb 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java @@ -30,10 +30,16 @@ */ package org.thingsboard.server.common.transport.activity.strategy; -import lombok.EqualsAndHashCode; +public final class LastEventActivityStrategy implements ActivityStrategy { -@EqualsAndHashCode -public class LastEventActivityStrategy implements ActivityStrategy { + private static final LastEventActivityStrategy INSTANCE = new LastEventActivityStrategy(); + + private LastEventActivityStrategy() { + } + + public static LastEventActivityStrategy getInstance() { + return INSTANCE; + } @Override public boolean onActivity() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index da0ef3ca99..fb14936ad5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -74,7 +74,8 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.activity.AbstractActivityManager; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyFactory; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyType; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; @@ -158,7 +159,7 @@ public class DefaultTransportService extends AbstractActivityManager state = new ActivityState<>(); state.setMetadata(session.getSessionInfo()); - state.setStrategy(ActivityStrategyFactory.createStrategy(reportingStrategyName)); return state; } + @Override + protected ActivityStrategy getStrategy() { + return reportingStrategyType.toStrategy(); + } + @Override protected ActivityState updateState(UUID sessionId, ActivityState state) { SessionMetaData session = sessions.get(sessionId); diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java deleted file mode 100644 index c6eda7eb25..0000000000 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactoryTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.common.transport.activity.strategy; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class ActivityStrategyFactoryTest { - - @Test - public void testCreateAllEventsStrategy() { - ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("ALL"); - assertInstanceOf(AllEventsActivityStrategy.class, strategy, "Should return an instance of AllEventsActivityStrategy."); - } - - @Test - public void testCreateFirstEventStrategy() { - ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("FIRST"); - assertInstanceOf(FirstEventActivityStrategy.class, strategy, "Should return an instance of FirstEventActivityStrategy."); - } - - @Test - public void testCreateLastEventStrategy() { - ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("LAST"); - assertInstanceOf(LastEventActivityStrategy.class, strategy, "Should return an instance of LastEventActivityStrategy."); - } - - @Test - public void testCreateFirstAndLastEventStrategy() { - ActivityStrategy strategy = ActivityStrategyFactory.createStrategy("FIRST_AND_LAST"); - assertInstanceOf(FirstAndLastEventActivityStrategy.class, strategy, "Should return an instance of FirstAndLastEventActivityStrategy."); - } - - @Test - public void testCreateUnknownStrategy() { - assertThrows(IllegalArgumentException.class, () -> ActivityStrategyFactory.createStrategy("UNKNOWN"), - "Should throw IllegalArgumentException for unknown strategy names."); - } - -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java similarity index 62% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java rename to common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java index f4619e2afd..854659c1bf 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyFactory.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java @@ -30,29 +30,30 @@ */ package org.thingsboard.server.common.transport.activity.strategy; -public final class ActivityStrategyFactory { +import org.junit.jupiter.api.Test; - private static final String ALL_EVENTS_STRATEGY_NAME = "ALL"; - private static final String FIRST_EVENT_STRATEGY_NAME = "FIRST"; - private static final String LAST_EVENT_STRATEGY_NAME = "LAST"; - private static final String FIRST_AND_LAST_STRATEGY_NAME = "FIRST_AND_LAST"; +import static org.assertj.core.api.Assertions.assertThat; - private ActivityStrategyFactory() { +public class ActivityStrategyTypeTest { + + @Test + public void testCreateAllEventsStrategy() { + assertThat(ActivityStrategyType.ALL.toStrategy()).isEqualTo(AllEventsActivityStrategy.getInstance()); + } + + @Test + public void testCreateFirstEventStrategy() { + assertThat(ActivityStrategyType.FIRST.toStrategy()).isEqualTo(new FirstEventActivityStrategy()); + } + + @Test + public void testCreateLastEventStrategy() { + assertThat(ActivityStrategyType.LAST.toStrategy()).isEqualTo(LastEventActivityStrategy.getInstance()); } - public static ActivityStrategy createStrategy(String strategyName) { - switch (strategyName) { - case ALL_EVENTS_STRATEGY_NAME: - return new AllEventsActivityStrategy(); - case FIRST_EVENT_STRATEGY_NAME: - return new FirstEventActivityStrategy(); - case LAST_EVENT_STRATEGY_NAME: - return new LastEventActivityStrategy(); - case FIRST_AND_LAST_STRATEGY_NAME: - return new FirstAndLastEventActivityStrategy(); - default: - throw new IllegalArgumentException("Unknown activity strategy name: [" + strategyName + "."); - } + @Test + public void testCreateFirstAndLastEventStrategy() { + assertThat(ActivityStrategyType.FIRST_AND_LAST.toStrategy()).isEqualTo(new FirstAndLastEventActivityStrategy()); } } diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java index 22cbc10ca2..01f4bbcef1 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java @@ -41,7 +41,7 @@ public class AllEventsActivityStrategyTest { @BeforeEach public void setUp() { - strategy = new AllEventsActivityStrategy(); + strategy = AllEventsActivityStrategy.getInstance(); } @Test diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java index 51da85a9c3..7b49c2215b 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java @@ -42,7 +42,7 @@ public class LastEventActivityStrategyTest { @BeforeEach public void setUp() { - strategy = new LastEventActivityStrategy(); + strategy = LastEventActivityStrategy.getInstance(); } @Test diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java index 6ad48043ce..c966cc5535 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -45,10 +46,7 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.activity.ActivityReportCallback; import org.thingsboard.server.common.transport.activity.ActivityState; import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.AllEventsActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.FirstAndLastEventActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.FirstEventActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.LastEventActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyType; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.UUID; @@ -60,9 +58,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EVENT_MSG_CLOSED; import static org.thingsboard.server.common.transport.service.DefaultTransportService.SESSION_EXPIRED_NOTIFICATION_PROTO; @@ -195,25 +191,18 @@ public class TransportActivityManagerTest { verify(transportServiceMock).onActivity(SESSION_ID); } - @ParameterizedTest - @MethodSource("provideTestParamsForCreateNewState") - void givenDifferentReportingStrategies_whenCreatingNewState_thenShouldCreateEmptyStateWithCorrectStrategy( - String reportingStrategyName, ActivityStrategy reportingStrategy - ) { + @Test + void givenKey_whenCreatingNewState_thenShouldCorrectlyCreateNewEmptyState() { // GIVEN TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() .setSessionIdMSB(SESSION_ID.getMostSignificantBits()) .setSessionIdLSB(SESSION_ID.getLeastSignificantBits()) .build(); - SessionMsgListener listenerMock = mock(SessionMsgListener.class); - sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); - - ReflectionTestUtils.setField(transportServiceMock, "reportingStrategyName", reportingStrategyName); + sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, null)); when(transportServiceMock.createNewState(SESSION_ID)).thenCallRealMethod(); ActivityState expectedState = new ActivityState<>(); - expectedState.setStrategy(reportingStrategy); expectedState.setMetadata(sessionInfo); // WHEN @@ -223,13 +212,18 @@ public class TransportActivityManagerTest { assertThat(actualState).isEqualTo(expectedState); } - private static Stream provideTestParamsForCreateNewState() { - return Stream.of( - Arguments.of("ALL", new AllEventsActivityStrategy()), - Arguments.of("FIRST", new FirstEventActivityStrategy()), - Arguments.of("LAST", new LastEventActivityStrategy()), - Arguments.of("FIRST_AND_LAST", new FirstAndLastEventActivityStrategy()) - ); + @ParameterizedTest + @EnumSource(ActivityStrategyType.class) + void givenDifferentReportingStrategies_whenGettingStrategy_thenShouldReturnCorrectStrategy(ActivityStrategyType reportingStrategyType) { + // GIVEN + doCallRealMethod().when(transportServiceMock).getStrategy(); + ReflectionTestUtils.setField(transportServiceMock, "reportingStrategyType", reportingStrategyType); + + // WHEN + ActivityStrategy actualStrategy = transportServiceMock.getStrategy(); + + // THEN + assertThat(actualStrategy).isEqualTo(reportingStrategyType.toStrategy()); } @Test @@ -242,9 +236,7 @@ public class TransportActivityManagerTest { ActivityState state = new ActivityState<>(); state.setLastRecordedTime(123L); - state.setLastReportedTime(312L); state.setMetadata(sessionInfo); - state.setStrategy(new FirstEventActivityStrategy()); when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); @@ -266,14 +258,10 @@ public class TransportActivityManagerTest { sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); long lastRecordedTime = 123L; - long lastReportedTime = 312L; - ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); ActivityState state = new ActivityState<>(); state.setLastRecordedTime(lastRecordedTime); - state.setLastReportedTime(lastReportedTime); state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); - state.setStrategy(strategySpy); when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); @@ -283,10 +271,7 @@ public class TransportActivityManagerTest { // THEN assertThat(updatedState).isSameAs(state); assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); - assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); - assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); - verifyNoInteractions(strategySpy); } @Test @@ -303,14 +288,10 @@ public class TransportActivityManagerTest { sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); long lastRecordedTime = 123L; - long lastReportedTime = 312L; - ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); ActivityState state = new ActivityState<>(); state.setLastRecordedTime(lastRecordedTime); - state.setLastReportedTime(lastReportedTime); state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); - state.setStrategy(strategySpy); when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); @@ -320,10 +301,7 @@ public class TransportActivityManagerTest { // THEN assertThat(updatedState).isSameAs(state); assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); - assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); - assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); - verifyNoInteractions(strategySpy); verify(transportServiceMock, never()).getLastRecordedTime(gwSessionId); } @@ -349,14 +327,10 @@ public class TransportActivityManagerTest { sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); long lastRecordedTime = 123L; - long lastReportedTime = 312L; - ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); ActivityState state = new ActivityState<>(); state.setLastRecordedTime(lastRecordedTime); - state.setLastReportedTime(lastReportedTime); state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); - state.setStrategy(strategySpy); when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); @@ -366,10 +340,7 @@ public class TransportActivityManagerTest { // THEN assertThat(updatedState).isSameAs(state); assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); - assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); - assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); - verifyNoInteractions(strategySpy); verify(transportServiceMock, never()).getLastRecordedTime(gwSessionId); } @@ -400,14 +371,10 @@ public class TransportActivityManagerTest { sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); long lastRecordedTime = 123L; - long lastReportedTime = 312L; - ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); ActivityState state = new ActivityState<>(); state.setLastRecordedTime(lastRecordedTime); - state.setLastReportedTime(lastReportedTime); state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); - state.setStrategy(strategySpy); when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); @@ -417,10 +384,7 @@ public class TransportActivityManagerTest { // THEN assertThat(updatedState).isSameAs(state); assertThat(updatedState.getLastRecordedTime()).isEqualTo(gwLastRecordedTime); - assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); - assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); - verifyNoInteractions(strategySpy); } @Test @@ -449,14 +413,10 @@ public class TransportActivityManagerTest { sessions.put(SESSION_ID, new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listenerMock)); long lastRecordedTime = 123L; - long lastReportedTime = 312L; - ActivityStrategy strategySpy = spy(new FirstEventActivityStrategy()); ActivityState state = new ActivityState<>(); state.setLastRecordedTime(lastRecordedTime); - state.setLastReportedTime(lastReportedTime); state.setMetadata(TransportProtos.SessionInfoProto.getDefaultInstance()); - state.setStrategy(strategySpy); when(transportServiceMock.updateState(SESSION_ID, state)).thenCallRealMethod(); @@ -466,10 +426,7 @@ public class TransportActivityManagerTest { // THEN assertThat(updatedState).isSameAs(state); assertThat(updatedState.getLastRecordedTime()).isEqualTo(lastRecordedTime); - assertThat(updatedState.getLastReportedTime()).isEqualTo(lastReportedTime); assertThat(updatedState.getMetadata()).isEqualTo(sessionInfo); - assertThat(updatedState.getStrategy()).isEqualTo(strategySpy); - verifyNoInteractions(strategySpy); } @ParameterizedTest From 47c72fb4e52ff1ff166135d43cd728049963b5f6 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 18 Dec 2023 10:16:15 +0200 Subject: [PATCH 24/31] Refactor: remove explicit init method from activity manager interface --- .../activity/AbstractActivityManager.java | 40 ++++++------------- .../transport/activity/ActivityManager.java | 2 - .../service/DefaultTransportService.java | 16 +++++--- 3 files changed, 22 insertions(+), 36 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 2124eb13d2..6c41ea70ea 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -33,7 +33,6 @@ package org.thingsboard.server.common.transport.activity; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; import org.thingsboard.server.queue.scheduler.SchedulerComponent; @@ -52,9 +51,6 @@ public abstract class AbstractActivityManager implements Activity @Autowired protected SchedulerComponent scheduler; - protected String name; - private boolean initialized; - @Data private class ActivityStateWrapper { @@ -64,21 +60,13 @@ public abstract class AbstractActivityManager implements Activity } - @Override - public synchronized void init(String name, long reportingPeriodMillis) { - if (!initialized) { - this.name = StringUtils.notBlankOrDefault(name, "activity-manager"); - log.info("Activity manager with name [{}] is initializing.", this.name); - if (reportingPeriodMillis <= 0) { - reportingPeriodMillis = 3000; - log.error("[{}] Negative or zero reporting period millisecond was provided. Going to use reporting period value of 3 seconds.", this.name); - } - scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodMillis), reportingPeriodMillis, TimeUnit.MILLISECONDS); - initialized = true; - log.info("Activity manager with name [{}] is initialized.", this.name); - } + protected void init() { + var reportingPeriodMillis = getReportingPeriodMillis(); + scheduler.scheduleAtFixedRate(this::onReportingPeriodEnd, new Random().nextInt((int) reportingPeriodMillis), reportingPeriodMillis, TimeUnit.MILLISECONDS); } + protected abstract long getReportingPeriodMillis(); + protected abstract ActivityState createNewState(Key key); protected abstract ActivityStrategy getStrategy(); @@ -93,15 +81,11 @@ public abstract class AbstractActivityManager implements Activity @Override public void onActivity(Key key) { - if (!initialized) { - log.error("[{}] Failed to process activity event: activity manager is not initialized.", name); - return; - } if (key == null) { - log.error("[{}] Failed to process activity event: provided activity key is null.", name); + log.error("Failed to process activity event: provided activity key is null."); return; } - log.debug("[{}] Received activity event for key: [{}]", name, key); + log.debug("Received activity event for key: [{}]", key); long newLastRecordedTime = System.currentTimeMillis(); var shouldReport = new AtomicBoolean(false); @@ -131,7 +115,7 @@ public abstract class AbstractActivityManager implements Activity long lastRecordedTime = activityState.getLastRecordedTime(); long lastReportedTime = activityStateWrapper.getLastReportedTime(); if (shouldReport.get() && lastReportedTime < lastRecordedTime) { - log.debug("[{}] Going to report first activity event for key: [{}].", name, key); + log.debug("Going to report first activity event for key: [{}].", key); reportActivity(key, activityState.getMetadata(), lastRecordedTime, new ActivityReportCallback<>() { @Override public void onSuccess(Key key, long reportedTime) { @@ -140,7 +124,7 @@ public abstract class AbstractActivityManager implements Activity @Override public void onFailure(Key key, Throwable t) { - log.debug("[{}] Failed to report first activity event for key: [{}].", name, key, t); + log.debug("Failed to report first activity event for key: [{}].", key, t); } }); } @@ -153,7 +137,7 @@ public abstract class AbstractActivityManager implements Activity } private void onReportingPeriodEnd() { - log.debug("[{}] Going to end reporting period.", name); + log.debug("Going to end reporting period."); for (Map.Entry entry : states.entrySet()) { var key = entry.getKey(); var stateWrapper = entry.getValue(); @@ -185,7 +169,7 @@ public abstract class AbstractActivityManager implements Activity } if (shouldReport && lastReportedTime < lastRecordedTime) { - log.debug("[{}] Going to report last activity event for key: [{}].", name, key); + log.debug("Going to report last activity event for key: [{}].", key); reportActivity(key, metadata, lastRecordedTime, new ActivityReportCallback<>() { @Override public void onSuccess(Key key, long reportedTime) { @@ -194,7 +178,7 @@ public abstract class AbstractActivityManager implements Activity @Override public void onFailure(Key key, Throwable t) { - log.debug("[{}] Failed to report last activity event for key: [{}].", name, key, t); + log.debug("Failed to report last activity event for key: [{}].", key, t); } }); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index 847e8f4472..f8881d1b61 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -32,8 +32,6 @@ package org.thingsboard.server.common.transport.activity; public interface ActivityManager { - void init(String name, long reportingPeriodMillis); - void onActivity(Key key); long getLastRecordedTime(Key key); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index fb14936ad5..f733efef67 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -138,7 +138,6 @@ public class DefaultTransportService extends AbstractActivityManager createNewState(UUID sessionId) { SessionMetaData session = sessions.get(sessionId); @@ -827,9 +831,9 @@ public class DefaultTransportService extends AbstractActivityManager callback) { - log.debug("[{}] Reporting activity state for session with id: [{}]. Time to report: [{}].", name, sessionId, timeToReport); + log.debug("Reporting activity state for session with id: [{}]. Time to report: [{}].", sessionId, timeToReport); SessionMetaData session = sessions.get(sessionId); TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(session != null && session.isSubscribedToAttributes()) From a31bd5e2c59dc8a37318f619c0d0848b5aafd9d4 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 18 Dec 2023 13:40:32 +0200 Subject: [PATCH 25/31] Add tests for onActivity() in integration activity manager, add concurrency fix in onActivity() --- .../IntegrationActivityManagerTest.java | 190 +++++++++++++++++- .../activity/AbstractActivityManager.java | 29 +-- .../transport/activity/ActivityManager.java | 4 +- .../service/DefaultTransportService.java | 2 +- .../service/TransportActivityManagerTest.java | 4 +- 5 files changed, 211 insertions(+), 18 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java index 3b85261bac..c63b267fbf 100644 --- a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java +++ b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java @@ -55,15 +55,20 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; @@ -78,6 +83,182 @@ public class IntegrationActivityManagerTest { @Mock private DefaultPlatformIntegrationService integrationServiceMock; + @Test + void givenKeyIsNull_whenOnActivity_thenShouldNotRecordAndShouldNotReport() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + IntegrationActivityKey key = null; + + doCallRealMethod().when(integrationServiceMock).onActivity(eq(key), anyLong()); + + // WHEN + integrationServiceMock.onActivity(key, 123L); + + // THEN + assertThat(states.isEmpty()).isTrue(); + verify(integrationServiceMock, never()).createNewState(any()); + verify(integrationServiceMock, never()).getStrategy(); + verify(integrationServiceMock, never()).reportActivity(any(), any(), anyLong(), any()); + } + + @Test + void givenNewActivity_whenOnActivity_thenShouldCreateNewStateAndRecord() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + + long activityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); + + when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); + + // WHEN + integrationServiceMock.onActivity(key, activityTime); + + // THEN + assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(activityTime); + verify(integrationServiceMock).createNewState(key); + verify(integrationServiceMock).getStrategy(); + } + + @Test + void givenSubsequentActivities_whenOnActivity_thenShouldRecordBoth() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + + when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); + + // WHEN-THEN + long firstActivityTime = 100L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(firstActivityTime); + + long secondActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(secondActivityTime); + } + + @Test + void givenActivityAndStrategySaysThatShouldNotReport_whenOnActivity_thenShouldNotReport() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + when(strategyMock.onActivity()).thenReturn(false); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long activityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); + + // WHEN + integrationServiceMock.onActivity(key, activityTime); + + // THEN + verify(integrationServiceMock, never()).reportActivity(any(), any(), anyLong(), any()); + } + + @Test + void givenActivityAndStrategySaysThatShouldReport_whenOnActivity_thenShouldReport() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + when(strategyMock.onActivity()).thenReturn(true); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long activityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); + + // WHEN + integrationServiceMock.onActivity(key, activityTime); + + // THEN + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), any()); + } + + @Test + void givenActivityAndStrategySaysThatShouldReportButLastRecordedTimeIsEqualToLastReportedTime_whenOnActivity_thenShouldNotReport() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + when(strategyMock.onActivity()).thenReturn(true); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + // WHEN-THEN + long firstActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); + + long secondActivityTime = 100L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + + verify(integrationServiceMock, never()).reportActivity(eq(key), any(), eq(secondActivityTime), any()); + } + + @Test + void givenActivityAndStrategySaysThatShouldReportAndLastRecordedTimeIsGreaterThanLastReportedTime_whenOnActivity_thenShouldReport() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + when(strategyMock.onActivity()).thenReturn(true); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + // WHEN-THEN + long firstActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); + + long secondActivityTime = 456L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); + } + @Test void givenKeyAndTimeToReport_whenReportingActivity_thenShouldCorrectlyReportActivity() { // GIVEN @@ -148,12 +329,14 @@ public class IntegrationActivityManagerTest { .build(); TransportProtos.PostTelemetryMsg postTelemetryMsg = TransportProtos.PostTelemetryMsg.getDefaultInstance(); doCallRealMethod().when(integrationServiceMock).process(sessionInfo, postTelemetryMsg, null); + long expectedTime = 123L; + when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(expectedTime); // WHEN integrationServiceMock.process(sessionInfo, postTelemetryMsg, null); // THEN - verify(integrationServiceMock).onActivity(key); + verify(integrationServiceMock).onActivity(key, expectedTime); } @Test @@ -172,11 +355,14 @@ public class IntegrationActivityManagerTest { doCallRealMethod().when(integrationServiceMock).process(sessionInfo, postAttributeMsg, null); doNothing().when(integrationServiceMock).sendToRuleEngine(any(), any(), any(), any(), any(), any(), any()); + long activityTime = 123L; + when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(activityTime); + // WHEN integrationServiceMock.process(sessionInfo, postAttributeMsg, null); // THEN - verify(integrationServiceMock).onActivity(key); + verify(integrationServiceMock).onActivity(key, activityTime); } @Test diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 6c41ea70ea..80ab8d8665 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -42,6 +42,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; @Slf4j public abstract class AbstractActivityManager implements ActivityManager { @@ -80,15 +81,17 @@ public abstract class AbstractActivityManager implements Activity protected abstract void reportActivity(Key key, Metadata metadata, long timeToReport, ActivityReportCallback callback); @Override - public void onActivity(Key key) { + public void onActivity(Key key, long newLastRecordedTime) { if (key == null) { log.error("Failed to process activity event: provided activity key is null."); return; } log.debug("Received activity event for key: [{}]", key); - long newLastRecordedTime = System.currentTimeMillis(); var shouldReport = new AtomicBoolean(false); + var lastRecordedTime = new AtomicLong(); + var lastReportedTime = new AtomicLong(); + var activityStateWrapper = states.compute(key, (__, stateWrapper) -> { if (stateWrapper == null) { var newState = createNewState(key); @@ -104,6 +107,8 @@ public abstract class AbstractActivityManager implements Activity state.setLastRecordedTime(newLastRecordedTime); } shouldReport.set(stateWrapper.getStrategy().onActivity()); + lastRecordedTime.set(state.getLastRecordedTime()); + lastReportedTime.set(stateWrapper.getLastReportedTime()); return stateWrapper; }); @@ -111,12 +116,9 @@ public abstract class AbstractActivityManager implements Activity return; } - var activityState = activityStateWrapper.getState(); - long lastRecordedTime = activityState.getLastRecordedTime(); - long lastReportedTime = activityStateWrapper.getLastReportedTime(); - if (shouldReport.get() && lastReportedTime < lastRecordedTime) { + if (shouldReport.get() && lastReportedTime.get() < lastRecordedTime.get()) { log.debug("Going to report first activity event for key: [{}].", key); - reportActivity(key, activityState.getMetadata(), lastRecordedTime, new ActivityReportCallback<>() { + reportActivity(key, activityStateWrapper.getState().getMetadata(), lastRecordedTime.get(), new ActivityReportCallback<>() { @Override public void onSuccess(Key key, long reportedTime) { updateLastReportedTime(key, reportedTime); @@ -131,12 +133,7 @@ public abstract class AbstractActivityManager implements Activity } @Override - public long getLastRecordedTime(Key key) { - ActivityStateWrapper stateWrapper = states.get(key); - return stateWrapper == null ? 0L : stateWrapper.getState().getLastRecordedTime(); - } - - private void onReportingPeriodEnd() { + public void onReportingPeriodEnd() { log.debug("Going to end reporting period."); for (Map.Entry entry : states.entrySet()) { var key = entry.getKey(); @@ -185,6 +182,12 @@ public abstract class AbstractActivityManager implements Activity } } + @Override + public long getLastRecordedTime(Key key) { + ActivityStateWrapper stateWrapper = states.get(key); + return stateWrapper == null ? 0L : stateWrapper.getState().getLastRecordedTime(); + } + private void updateLastReportedTime(Key key, long newLastReportedTime) { states.computeIfPresent(key, (__, stateWrapper) -> { stateWrapper.setLastReportedTime(Math.max(stateWrapper.getLastReportedTime(), newLastReportedTime)); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index f8881d1b61..c55c94cc54 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -32,7 +32,9 @@ package org.thingsboard.server.common.transport.activity; public interface ActivityManager { - void onActivity(Key key); + void onActivity(Key key, long activityTimeMillis); + + void onReportingPeriodEnd(); long getLastRecordedTime(Key key); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index f733efef67..dc58c79250 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -785,7 +785,7 @@ public class DefaultTransportService extends AbstractActivityManager Date: Tue, 19 Dec 2023 12:18:21 +0200 Subject: [PATCH 26/31] Add tests for onReportingPeriodEnd() in integration activity manager --- .../IntegrationActivityManagerTest.java | 383 ++++++++++++++++++ .../activity/AbstractActivityManager.java | 6 +- 2 files changed, 388 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java index c63b267fbf..3cb40d3cb3 100644 --- a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java +++ b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java @@ -70,8 +70,10 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -259,6 +261,387 @@ public class IntegrationActivityManagerTest { verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); } + @Test + void givenUpdatedStateIsNullAndHasUnreportedEvent_whenOnReportingPeriodEnd_thenShouldRemoveStateAndReportEvent() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + + // first reported event + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(strategyMock.onActivity()).thenReturn(true); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long firstActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); + + // second unreported event + long secondActivityTime = 456L; + when(strategyMock.onActivity()).thenReturn(false); + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + + verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); + + verify(strategyMock, times(2)).onActivity(); + + long lastRecordedTime = secondActivityTime; + // lastReportedTime = firstActivityTime = 123L; + ActivityState updatedState = null; + + when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); + + // WHEN + doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); + integrationServiceMock.onReportingPeriodEnd(); + + // THEN + assertThat(states).isEmpty(); + verify(integrationServiceMock, never()).hasExpired(anyLong()); + verifyNoMoreInteractions(strategyMock); + verify(integrationServiceMock, never()).onStateExpiry(any(), any()); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(lastRecordedTime), any()); + } + + @Test + void givenUpdatedStateIsNullAndDoesntHaveUnreportedEvent_whenOnReportingPeriodEnd_thenShouldRemoveStateAndShouldNotAnything() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + + // reported event + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(strategyMock.onActivity()).thenReturn(true); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long activityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); + integrationServiceMock.onActivity(key, activityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, activityTime); + + verify(strategyMock).onActivity(); + + long lastRecordedTime = activityTime; + // lastReportedTime = lastRecordedTime = 123L; + ActivityState updatedState = null; + + when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); + + // WHEN + doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); + integrationServiceMock.onReportingPeriodEnd(); + + // THEN + assertThat(states).isEmpty(); + verify(integrationServiceMock, never()).hasExpired(anyLong()); + verifyNoMoreInteractions(strategyMock); + verify(integrationServiceMock, never()).onStateExpiry(any(), any()); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(lastRecordedTime), any()); + } + + @Test + void givenUpdatedStateIsNotNullAndHasNotExpiredAndStrategySaysThatShouldReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldReportEventUsingUpdatedState() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + + // first reported event + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(strategyMock.onActivity()).thenReturn(true); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long firstActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); + + // second unreported event + long secondActivityTime = 456L; + when(strategyMock.onActivity()).thenReturn(false); + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + + verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); + + verify(strategyMock, times(2)).onActivity(); + + long updatedLastRecordedTime = 500L; + // lastReportedTime = firstActivityTime = 123L; + ActivityState updatedState = new ActivityState<>(); + updatedState.setLastRecordedTime(updatedLastRecordedTime); + + when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); + when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(false); + when(strategyMock.onReportingPeriodEnd()).thenReturn(true); + + // WHEN + doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); + integrationServiceMock.onReportingPeriodEnd(); + + // THEN + assertThat(states).containsKey(key); + + when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); + assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(updatedLastRecordedTime); + + verify(integrationServiceMock, never()).onStateExpiry(any(), any()); + + verify(strategyMock).onReportingPeriodEnd(); + verifyNoMoreInteractions(strategyMock); + + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); + } + + @Test + void givenUpdatedStateIsNotNullAndHasNotExpiredAndStrategySaysThatShouldReportAndDoesNotHaveUnreportedEvents_whenOnReportingPeriodEnd_thenShouldNotReportAnything() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + + // first reported event + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(strategyMock.onActivity()).thenReturn(true); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long activityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); + integrationServiceMock.onActivity(key, activityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, activityTime); + + verify(strategyMock).onActivity(); + + long updatedLastRecordedTime = 123L; + // lastReportedTime = firstActivityTime = 123L; + ActivityState updatedState = new ActivityState<>(); + updatedState.setLastRecordedTime(updatedLastRecordedTime); + + when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); + when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(false); + when(strategyMock.onReportingPeriodEnd()).thenReturn(true); + + // WHEN + doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); + integrationServiceMock.onReportingPeriodEnd(); + + // THEN + assertThat(states).containsKey(key); + + when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); + assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(updatedLastRecordedTime); + + verify(integrationServiceMock, never()).onStateExpiry(any(), any()); + + verify(strategyMock).onReportingPeriodEnd(); + verifyNoMoreInteractions(strategyMock); + + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), any()); + } + + @Test + void givenUpdatedStateIsNotNullAndHasNotExpiredAndStrategySaysThatShouldNotReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldNotReportAnything() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + + // first reported event + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(strategyMock.onActivity()).thenReturn(true); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long firstActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); + + // second unreported event + long secondActivityTime = 456L; + when(strategyMock.onActivity()).thenReturn(false); + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + + verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); + + verify(strategyMock, times(2)).onActivity(); + + long updatedLastRecordedTime = 500L; + // lastReportedTime = firstActivityTime = 123L; + ActivityState updatedState = new ActivityState<>(); + updatedState.setLastRecordedTime(updatedLastRecordedTime); + + when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); + when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(false); + when(strategyMock.onReportingPeriodEnd()).thenReturn(false); + + // WHEN + doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); + integrationServiceMock.onReportingPeriodEnd(); + + // THEN + assertThat(states).containsKey(key); + + when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); + assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(updatedLastRecordedTime); + + verify(integrationServiceMock, never()).onStateExpiry(any(), any()); + + verify(strategyMock).onReportingPeriodEnd(); + verifyNoMoreInteractions(strategyMock); + + verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); + } + + @Test + void givenUpdatedStateIsNotNullAndHasExpiredAndStrategySaysThatShouldReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldReportEventUsingUpdatedState() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + + // first reported event + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(strategyMock.onActivity()).thenReturn(true); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long firstActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); + + // second unreported event + long secondActivityTime = 456L; + when(strategyMock.onActivity()).thenReturn(false); + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + + verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); + + verify(strategyMock, times(2)).onActivity(); + + long updatedLastRecordedTime = 500L; + // lastReportedTime = firstActivityTime = 123L; + ActivityState updatedState = new ActivityState<>(); + updatedState.setLastRecordedTime(updatedLastRecordedTime); + + when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); + when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(true); + when(strategyMock.onReportingPeriodEnd()).thenReturn(true); + + // WHEN + doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); + integrationServiceMock.onReportingPeriodEnd(); + + // THEN + assertThat(states).isEmpty(); + + verify(integrationServiceMock).onStateExpiry(key, null); + + when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); + assertThat(integrationServiceMock.getLastRecordedTime(key)).isZero(); + + verify(strategyMock).onReportingPeriodEnd(); + verifyNoMoreInteractions(strategyMock); + + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); + } + + @Test + void givenUpdatedStateIsNotNullAndHasExpiredAndStrategySaysThatShouldNotReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldReportEventUsingUpdatedState() { + // GIVEN + ConcurrentMap states = new ConcurrentHashMap<>(); + ReflectionTestUtils.setField(integrationServiceMock, "states", states); + + var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); + ActivityStrategy strategyMock = mock(ActivityStrategy.class); + + // first reported event + when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); + when(strategyMock.onActivity()).thenReturn(true); + when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); + + long firstActivityTime = 123L; + doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); + integrationServiceMock.onActivity(key, firstActivityTime); + + ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); + firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); + + // second unreported event + long secondActivityTime = 456L; + when(strategyMock.onActivity()).thenReturn(false); + doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); + integrationServiceMock.onActivity(key, secondActivityTime); + + verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); + + verify(strategyMock, times(2)).onActivity(); + + long updatedLastRecordedTime = 500L; + // lastReportedTime = firstActivityTime = 123L; + ActivityState updatedState = new ActivityState<>(); + updatedState.setLastRecordedTime(updatedLastRecordedTime); + + when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); + when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(true); + when(strategyMock.onReportingPeriodEnd()).thenReturn(false); + + // WHEN + doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); + integrationServiceMock.onReportingPeriodEnd(); + + // THEN + assertThat(states).isEmpty(); + + verify(integrationServiceMock).onStateExpiry(key, null); + + when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); + assertThat(integrationServiceMock.getLastRecordedTime(key)).isZero(); + + verify(strategyMock).onReportingPeriodEnd(); + verifyNoMoreInteractions(strategyMock); + + verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); + } + + // TODO: onReportingPeriodEnd() test for updating reported time + @Test void givenKeyAndTimeToReport_whenReportingActivity_thenShouldCorrectlyReportActivity() { // GIVEN diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 80ab8d8665..e9ed4cdcea 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -43,6 +43,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; @Slf4j public abstract class AbstractActivityManager implements ActivityManager { @@ -91,6 +92,7 @@ public abstract class AbstractActivityManager implements Activity var shouldReport = new AtomicBoolean(false); var lastRecordedTime = new AtomicLong(); var lastReportedTime = new AtomicLong(); + var metadata = new AtomicReference(); var activityStateWrapper = states.compute(key, (__, stateWrapper) -> { if (stateWrapper == null) { @@ -109,6 +111,7 @@ public abstract class AbstractActivityManager implements Activity shouldReport.set(stateWrapper.getStrategy().onActivity()); lastRecordedTime.set(state.getLastRecordedTime()); lastReportedTime.set(stateWrapper.getLastReportedTime()); + metadata.set(state.getMetadata()); return stateWrapper; }); @@ -118,7 +121,7 @@ public abstract class AbstractActivityManager implements Activity if (shouldReport.get() && lastReportedTime.get() < lastRecordedTime.get()) { log.debug("Going to report first activity event for key: [{}].", key); - reportActivity(key, activityStateWrapper.getState().getMetadata(), lastRecordedTime.get(), new ActivityReportCallback<>() { + reportActivity(key, metadata.get(), lastRecordedTime.get(), new ActivityReportCallback<>() { @Override public void onSuccess(Key key, long reportedTime) { updateLastReportedTime(key, reportedTime); @@ -149,6 +152,7 @@ public abstract class AbstractActivityManager implements Activity var updatedState = updateState(key, currentState); if (updatedState != null) { + stateWrapper.setState(updatedState); lastRecordedTime = updatedState.getLastRecordedTime(); metadata = updatedState.getMetadata(); hasExpired = hasExpired(lastRecordedTime); From 563debe4212f1e8e83b0c46943bfa1e1e7b73db1 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 11 Jan 2024 13:56:45 +0200 Subject: [PATCH 27/31] Fix license --- .../server/service/integration/IntegrationActivityKey.java | 2 +- .../service/integration/IntegrationActivityManagerTest.java | 2 +- .../common/transport/activity/AbstractActivityManager.java | 2 +- .../server/common/transport/activity/ActivityManager.java | 2 +- .../common/transport/activity/ActivityReportCallback.java | 2 +- .../common/transport/activity/strategy/ActivityStrategy.java | 2 +- .../transport/activity/strategy/ActivityStrategyType.java | 2 +- .../transport/activity/strategy/AllEventsActivityStrategy.java | 2 +- .../activity/strategy/FirstAndLastEventActivityStrategy.java | 2 +- .../transport/activity/strategy/FirstEventActivityStrategy.java | 2 +- .../transport/activity/strategy/LastEventActivityStrategy.java | 2 +- .../transport/activity/strategy/ActivityStrategyTypeTest.java | 2 +- .../activity/strategy/AllEventsActivityStrategyTest.java | 2 +- .../strategy/FirstAndLastEventActivityStrategyTest.java | 2 +- .../activity/strategy/FirstEventActivityStrategyTest.java | 2 +- .../activity/strategy/LastEventActivityStrategyTest.java | 2 +- .../common/transport/service/TransportActivityManagerTest.java | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java b/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java index d830d382cb..683a2d26f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java +++ b/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java index 3cb40d3cb3..f35fcc3bd0 100644 --- a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java +++ b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index e9ed4cdcea..89efec8e1c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index c55c94cc54..12eae33ee7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java index 9d009d2707..2b00495ed2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java index 1bb8dfdcaf..497ed31f9d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java index bda213936e..4db053f84a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java index 6af3469fd5..6fdbca31ec 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java index 71682da1ac..4b8234ac9b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java index 66c2b8a8e2..3e75154c3e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java index fd9fe1f3eb..b731aa275e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java index 854659c1bf..89232623f6 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java index 01f4bbcef1..2d550991a3 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java index 597693f926..a1cb7ac302 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java index 5d4d270f0f..585c9caeb1 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java index 7b49c2215b..441a88c32f 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java index e3e9ded47b..30395f28f4 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java @@ -1,7 +1,7 @@ /** * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. * * NOTICE: All information contained herein is, and remains * the property of ThingsBoard, Inc. and its suppliers, From 4bbe23f9c3af5b3530f1ea4c204d1be3ef6c115b Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 11 Jan 2024 16:36:18 +0200 Subject: [PATCH 28/31] Fix license --- .../integration/IntegrationActivityKey.java | 44 - .../IntegrationActivityManagerTest.java | 858 ------------------ .../activity/AbstractActivityManager.java | 35 +- .../transport/activity/ActivityManager.java | 35 +- .../activity/ActivityReportCallback.java | 35 +- .../transport/activity/ActivityState.java | 35 +- .../activity/strategy/ActivityStrategy.java | 35 +- .../strategy/ActivityStrategyType.java | 35 +- .../strategy/AllEventsActivityStrategy.java | 35 +- .../FirstAndLastEventActivityStrategy.java | 35 +- .../strategy/FirstEventActivityStrategy.java | 35 +- .../strategy/LastEventActivityStrategy.java | 35 +- .../strategy/ActivityStrategyTypeTest.java | 35 +- .../AllEventsActivityStrategyTest.java | 35 +- ...FirstAndLastEventActivityStrategyTest.java | 35 +- .../FirstEventActivityStrategyTest.java | 35 +- .../LastEventActivityStrategyTest.java | 35 +- .../service/TransportActivityManagerTest.java | 35 +- 18 files changed, 160 insertions(+), 1302 deletions(-) delete mode 100644 application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java delete mode 100644 application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java b/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java deleted file mode 100644 index 683a2d26f8..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/integration/IntegrationActivityKey.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.service.integration; - -import lombok.NonNull; -import lombok.Value; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; - -@Value -public class IntegrationActivityKey { - - @NonNull TenantId tenantId; - @NonNull DeviceId deviceId; - -} diff --git a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java b/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java deleted file mode 100644 index f35fcc3bd0..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/integration/IntegrationActivityManagerTest.java +++ /dev/null @@ -1,858 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.service.integration; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.EnumSource; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyType; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.TbQueueCallback; -import org.thingsboard.server.queue.TbQueueProducer; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.PartitionService; - -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class IntegrationActivityManagerTest { - - private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("1306648a-9b26-11ee-b9d1-0242ac120002")); - private final DeviceId DEVICE_ID = DeviceId.fromString("1d288a06-9b26-11ee-b9d1-0242ac120002"); - - @Mock - private DefaultPlatformIntegrationService integrationServiceMock; - - @Test - void givenKeyIsNull_whenOnActivity_thenShouldNotRecordAndShouldNotReport() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - IntegrationActivityKey key = null; - - doCallRealMethod().when(integrationServiceMock).onActivity(eq(key), anyLong()); - - // WHEN - integrationServiceMock.onActivity(key, 123L); - - // THEN - assertThat(states.isEmpty()).isTrue(); - verify(integrationServiceMock, never()).createNewState(any()); - verify(integrationServiceMock, never()).getStrategy(); - verify(integrationServiceMock, never()).reportActivity(any(), any(), anyLong(), any()); - } - - @Test - void givenNewActivity_whenOnActivity_thenShouldCreateNewStateAndRecord() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - - long activityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); - - when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); - - // WHEN - integrationServiceMock.onActivity(key, activityTime); - - // THEN - assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(activityTime); - verify(integrationServiceMock).createNewState(key); - verify(integrationServiceMock).getStrategy(); - } - - @Test - void givenSubsequentActivities_whenOnActivity_thenShouldRecordBoth() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - - when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); - - // WHEN-THEN - long firstActivityTime = 100L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(firstActivityTime); - - long secondActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(secondActivityTime); - } - - @Test - void givenActivityAndStrategySaysThatShouldNotReport_whenOnActivity_thenShouldNotReport() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - when(strategyMock.onActivity()).thenReturn(false); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long activityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); - - // WHEN - integrationServiceMock.onActivity(key, activityTime); - - // THEN - verify(integrationServiceMock, never()).reportActivity(any(), any(), anyLong(), any()); - } - - @Test - void givenActivityAndStrategySaysThatShouldReport_whenOnActivity_thenShouldReport() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - when(strategyMock.onActivity()).thenReturn(true); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long activityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); - - // WHEN - integrationServiceMock.onActivity(key, activityTime); - - // THEN - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), any()); - } - - @Test - void givenActivityAndStrategySaysThatShouldReportButLastRecordedTimeIsEqualToLastReportedTime_whenOnActivity_thenShouldNotReport() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - when(strategyMock.onActivity()).thenReturn(true); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - // WHEN-THEN - long firstActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); - - long secondActivityTime = 100L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - - verify(integrationServiceMock, never()).reportActivity(eq(key), any(), eq(secondActivityTime), any()); - } - - @Test - void givenActivityAndStrategySaysThatShouldReportAndLastRecordedTimeIsGreaterThanLastReportedTime_whenOnActivity_thenShouldReport() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - when(strategyMock.onActivity()).thenReturn(true); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - // WHEN-THEN - long firstActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); - - long secondActivityTime = 456L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); - } - - @Test - void givenUpdatedStateIsNullAndHasUnreportedEvent_whenOnReportingPeriodEnd_thenShouldRemoveStateAndReportEvent() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - - // first reported event - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(strategyMock.onActivity()).thenReturn(true); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long firstActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); - - // second unreported event - long secondActivityTime = 456L; - when(strategyMock.onActivity()).thenReturn(false); - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - - verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); - - verify(strategyMock, times(2)).onActivity(); - - long lastRecordedTime = secondActivityTime; - // lastReportedTime = firstActivityTime = 123L; - ActivityState updatedState = null; - - when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); - - // WHEN - doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); - integrationServiceMock.onReportingPeriodEnd(); - - // THEN - assertThat(states).isEmpty(); - verify(integrationServiceMock, never()).hasExpired(anyLong()); - verifyNoMoreInteractions(strategyMock); - verify(integrationServiceMock, never()).onStateExpiry(any(), any()); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(lastRecordedTime), any()); - } - - @Test - void givenUpdatedStateIsNullAndDoesntHaveUnreportedEvent_whenOnReportingPeriodEnd_thenShouldRemoveStateAndShouldNotAnything() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - - // reported event - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(strategyMock.onActivity()).thenReturn(true); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long activityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); - integrationServiceMock.onActivity(key, activityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, activityTime); - - verify(strategyMock).onActivity(); - - long lastRecordedTime = activityTime; - // lastReportedTime = lastRecordedTime = 123L; - ActivityState updatedState = null; - - when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); - - // WHEN - doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); - integrationServiceMock.onReportingPeriodEnd(); - - // THEN - assertThat(states).isEmpty(); - verify(integrationServiceMock, never()).hasExpired(anyLong()); - verifyNoMoreInteractions(strategyMock); - verify(integrationServiceMock, never()).onStateExpiry(any(), any()); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(lastRecordedTime), any()); - } - - @Test - void givenUpdatedStateIsNotNullAndHasNotExpiredAndStrategySaysThatShouldReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldReportEventUsingUpdatedState() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - - // first reported event - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(strategyMock.onActivity()).thenReturn(true); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long firstActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); - - // second unreported event - long secondActivityTime = 456L; - when(strategyMock.onActivity()).thenReturn(false); - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - - verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); - - verify(strategyMock, times(2)).onActivity(); - - long updatedLastRecordedTime = 500L; - // lastReportedTime = firstActivityTime = 123L; - ActivityState updatedState = new ActivityState<>(); - updatedState.setLastRecordedTime(updatedLastRecordedTime); - - when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); - when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(false); - when(strategyMock.onReportingPeriodEnd()).thenReturn(true); - - // WHEN - doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); - integrationServiceMock.onReportingPeriodEnd(); - - // THEN - assertThat(states).containsKey(key); - - when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); - assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(updatedLastRecordedTime); - - verify(integrationServiceMock, never()).onStateExpiry(any(), any()); - - verify(strategyMock).onReportingPeriodEnd(); - verifyNoMoreInteractions(strategyMock); - - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); - } - - @Test - void givenUpdatedStateIsNotNullAndHasNotExpiredAndStrategySaysThatShouldReportAndDoesNotHaveUnreportedEvents_whenOnReportingPeriodEnd_thenShouldNotReportAnything() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - - // first reported event - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(strategyMock.onActivity()).thenReturn(true); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long activityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, activityTime); - integrationServiceMock.onActivity(key, activityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, activityTime); - - verify(strategyMock).onActivity(); - - long updatedLastRecordedTime = 123L; - // lastReportedTime = firstActivityTime = 123L; - ActivityState updatedState = new ActivityState<>(); - updatedState.setLastRecordedTime(updatedLastRecordedTime); - - when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); - when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(false); - when(strategyMock.onReportingPeriodEnd()).thenReturn(true); - - // WHEN - doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); - integrationServiceMock.onReportingPeriodEnd(); - - // THEN - assertThat(states).containsKey(key); - - when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); - assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(updatedLastRecordedTime); - - verify(integrationServiceMock, never()).onStateExpiry(any(), any()); - - verify(strategyMock).onReportingPeriodEnd(); - verifyNoMoreInteractions(strategyMock); - - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(activityTime), any()); - } - - @Test - void givenUpdatedStateIsNotNullAndHasNotExpiredAndStrategySaysThatShouldNotReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldNotReportAnything() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - - // first reported event - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(strategyMock.onActivity()).thenReturn(true); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long firstActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); - - // second unreported event - long secondActivityTime = 456L; - when(strategyMock.onActivity()).thenReturn(false); - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - - verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); - - verify(strategyMock, times(2)).onActivity(); - - long updatedLastRecordedTime = 500L; - // lastReportedTime = firstActivityTime = 123L; - ActivityState updatedState = new ActivityState<>(); - updatedState.setLastRecordedTime(updatedLastRecordedTime); - - when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); - when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(false); - when(strategyMock.onReportingPeriodEnd()).thenReturn(false); - - // WHEN - doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); - integrationServiceMock.onReportingPeriodEnd(); - - // THEN - assertThat(states).containsKey(key); - - when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); - assertThat(integrationServiceMock.getLastRecordedTime(key)).isEqualTo(updatedLastRecordedTime); - - verify(integrationServiceMock, never()).onStateExpiry(any(), any()); - - verify(strategyMock).onReportingPeriodEnd(); - verifyNoMoreInteractions(strategyMock); - - verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); - } - - @Test - void givenUpdatedStateIsNotNullAndHasExpiredAndStrategySaysThatShouldReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldReportEventUsingUpdatedState() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - - // first reported event - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(strategyMock.onActivity()).thenReturn(true); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long firstActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); - - // second unreported event - long secondActivityTime = 456L; - when(strategyMock.onActivity()).thenReturn(false); - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - - verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); - - verify(strategyMock, times(2)).onActivity(); - - long updatedLastRecordedTime = 500L; - // lastReportedTime = firstActivityTime = 123L; - ActivityState updatedState = new ActivityState<>(); - updatedState.setLastRecordedTime(updatedLastRecordedTime); - - when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); - when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(true); - when(strategyMock.onReportingPeriodEnd()).thenReturn(true); - - // WHEN - doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); - integrationServiceMock.onReportingPeriodEnd(); - - // THEN - assertThat(states).isEmpty(); - - verify(integrationServiceMock).onStateExpiry(key, null); - - when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); - assertThat(integrationServiceMock.getLastRecordedTime(key)).isZero(); - - verify(strategyMock).onReportingPeriodEnd(); - verifyNoMoreInteractions(strategyMock); - - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); - } - - @Test - void givenUpdatedStateIsNotNullAndHasExpiredAndStrategySaysThatShouldNotReportAndHasUnreportedEvents_whenOnReportingPeriodEnd_thenShouldReportEventUsingUpdatedState() { - // GIVEN - ConcurrentMap states = new ConcurrentHashMap<>(); - ReflectionTestUtils.setField(integrationServiceMock, "states", states); - - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - ActivityStrategy strategyMock = mock(ActivityStrategy.class); - - // first reported event - when(integrationServiceMock.createNewState(key)).thenReturn(new ActivityState<>()); - when(strategyMock.onActivity()).thenReturn(true); - when(integrationServiceMock.getStrategy()).thenReturn(strategyMock); - - long firstActivityTime = 123L; - doCallRealMethod().when(integrationServiceMock).onActivity(key, firstActivityTime); - integrationServiceMock.onActivity(key, firstActivityTime); - - ArgumentCaptor> firstCallbackCaptor = ArgumentCaptor.forClass(ActivityReportCallback.class); - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(firstActivityTime), firstCallbackCaptor.capture()); - firstCallbackCaptor.getValue().onSuccess(key, firstActivityTime); - - // second unreported event - long secondActivityTime = 456L; - when(strategyMock.onActivity()).thenReturn(false); - doCallRealMethod().when(integrationServiceMock).onActivity(key, secondActivityTime); - integrationServiceMock.onActivity(key, secondActivityTime); - - verify(integrationServiceMock, never()).reportActivity(eq(key), isNull(), eq(secondActivityTime), any()); - - verify(strategyMock, times(2)).onActivity(); - - long updatedLastRecordedTime = 500L; - // lastReportedTime = firstActivityTime = 123L; - ActivityState updatedState = new ActivityState<>(); - updatedState.setLastRecordedTime(updatedLastRecordedTime); - - when(integrationServiceMock.updateState(eq(key), any())).thenReturn(updatedState); - when(integrationServiceMock.hasExpired(updatedLastRecordedTime)).thenReturn(true); - when(strategyMock.onReportingPeriodEnd()).thenReturn(false); - - // WHEN - doCallRealMethod().when(integrationServiceMock).onReportingPeriodEnd(); - integrationServiceMock.onReportingPeriodEnd(); - - // THEN - assertThat(states).isEmpty(); - - verify(integrationServiceMock).onStateExpiry(key, null); - - when(integrationServiceMock.getLastRecordedTime(key)).thenCallRealMethod(); - assertThat(integrationServiceMock.getLastRecordedTime(key)).isZero(); - - verify(strategyMock).onReportingPeriodEnd(); - verifyNoMoreInteractions(strategyMock); - - verify(integrationServiceMock).reportActivity(eq(key), isNull(), eq(updatedLastRecordedTime), any()); - } - - // TODO: onReportingPeriodEnd() test for updating reported time - - @Test - void givenKeyAndTimeToReport_whenReportingActivity_thenShouldCorrectlyReportActivity() { - // GIVEN - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - - TbQueueProducer> tbCoreMsgProducerMock = mock(TbQueueProducer.class); - ReflectionTestUtils.setField(integrationServiceMock, "tbCoreMsgProducer", tbCoreMsgProducerMock); - - PartitionService partitionServiceMock = mock(PartitionService.class); - ReflectionTestUtils.setField(integrationServiceMock, "partitionService", partitionServiceMock); - TopicPartitionInfo tpi = TopicPartitionInfo.builder().build(); - when(partitionServiceMock.resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID)).thenReturn(tpi); - - ActivityReportCallback callbackMock = mock(ActivityReportCallback.class); - - long expectedTime = 123L; - - doCallRealMethod().when(integrationServiceMock).reportActivity(key, null, expectedTime, callbackMock); - - // WHEN - integrationServiceMock.reportActivity(key, null, expectedTime, callbackMock); - - // THEN - verify(partitionServiceMock).resolve(ServiceType.TB_CORE, TENANT_ID, DEVICE_ID); - - ArgumentCaptor> msgCaptor = ArgumentCaptor.forClass(TbProtoQueueMsg.class); - ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass(TbQueueCallback.class); - verify(tbCoreMsgProducerMock).send(eq(tpi), msgCaptor.capture(), callbackCaptor.capture()); - - TbProtoQueueMsg queueMsg = msgCaptor.getValue(); - - TransportProtos.DeviceActivityProto expectedDeviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setLastActivityTime(expectedTime) - .build(); - - TransportProtos.ToCoreMsg expectedToCoreMsg = TransportProtos.ToCoreMsg.newBuilder() - .setDeviceActivityMsg(expectedDeviceActivityMsg) - .build(); - - assertThat(queueMsg.getKey()).isEqualTo(DEVICE_ID.getId()); - assertThat(queueMsg.getValue()).isEqualTo(expectedToCoreMsg); - - TbQueueCallback queueCallback = callbackCaptor.getValue(); - - queueCallback.onSuccess(null); - verify(callbackMock).onSuccess(key, expectedTime); - - var throwable = new Throwable(); - queueCallback.onFailure(throwable); - verify(callbackMock).onFailure(key, throwable); - } - - @Test - void givenPostTelemetryMsg_whenProcessingMsg_thenShouldCallOnActivity() { - // GIVEN - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setDeviceName("Test Device") - .setDeviceType("default") - .build(); - TransportProtos.PostTelemetryMsg postTelemetryMsg = TransportProtos.PostTelemetryMsg.getDefaultInstance(); - doCallRealMethod().when(integrationServiceMock).process(sessionInfo, postTelemetryMsg, null); - long expectedTime = 123L; - when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(expectedTime); - - // WHEN - integrationServiceMock.process(sessionInfo, postTelemetryMsg, null); - - // THEN - verify(integrationServiceMock).onActivity(key, expectedTime); - } - - @Test - void givenPostAttributesMsg_whenProcessingMsg_thenShouldCallOnActivity() { - // GIVEN - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - TransportProtos.SessionInfoProto sessionInfo = TransportProtos.SessionInfoProto.newBuilder() - .setTenantIdMSB(TENANT_ID.getId().getMostSignificantBits()) - .setTenantIdLSB(TENANT_ID.getId().getLeastSignificantBits()) - .setDeviceIdMSB(DEVICE_ID.getId().getMostSignificantBits()) - .setDeviceIdLSB(DEVICE_ID.getId().getLeastSignificantBits()) - .setDeviceName("Test Device") - .setDeviceType("default") - .build(); - TransportProtos.PostAttributeMsg postAttributeMsg = TransportProtos.PostAttributeMsg.getDefaultInstance(); - doCallRealMethod().when(integrationServiceMock).process(sessionInfo, postAttributeMsg, null); - doNothing().when(integrationServiceMock).sendToRuleEngine(any(), any(), any(), any(), any(), any(), any()); - - long activityTime = 123L; - when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(activityTime); - - // WHEN - integrationServiceMock.process(sessionInfo, postAttributeMsg, null); - - // THEN - verify(integrationServiceMock).onActivity(key, activityTime); - } - - @Test - void givenKey_whenCreatingNewState_thenShouldCorrectlyCreateNewEmptyState() { - // GIVEN - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - when(integrationServiceMock.createNewState(key)).thenCallRealMethod(); - - // WHEN - ActivityState actualNewState = integrationServiceMock.createNewState(key); - - // THEN - ActivityState expectedNewState = new ActivityState<>(); - assertThat(actualNewState).isEqualTo(expectedNewState); - } - - @ParameterizedTest - @EnumSource(ActivityStrategyType.class) - void givenDifferentReportingStrategies_whenGettingStrategy_thenShouldReturnCorrectStrategy(ActivityStrategyType reportingStrategyType) { - // GIVEN - doCallRealMethod().when(integrationServiceMock).getStrategy(); - ReflectionTestUtils.setField(integrationServiceMock, "reportingStrategyType", reportingStrategyType); - - // WHEN - ActivityStrategy actualStrategy = integrationServiceMock.getStrategy(); - - // THEN - assertThat(actualStrategy).isEqualTo(reportingStrategyType.toStrategy()); - } - - @Test - void givenActivityState_whenUpdatingActivityState_thenShouldReturnSameInstanceWithNoInteractions() { - // GIVEN - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - - ActivityState state = new ActivityState<>(); - state.setLastRecordedTime(123L); - ActivityState stateSpy = spy(state); - - when(integrationServiceMock.updateState(key, state)).thenCallRealMethod(); - - // WHEN - ActivityState updatedState = integrationServiceMock.updateState(key, state); - - // THEN - assertThat(updatedState).isSameAs(state); - verifyNoInteractions(stateSpy); - } - - @ParameterizedTest - @MethodSource("provideTestParamsForHasExpiredTrue") - public void givenExpiredLastRecordedTime_whenCheckingForExpiry_thenShouldReturnTrue(long currentTimeMillis, long lastRecordedTime, long reportingPeriodMillis) { - // GIVEN - ReflectionTestUtils.setField(integrationServiceMock, "reportingPeriodMillis", reportingPeriodMillis); - - when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(currentTimeMillis); - when(integrationServiceMock.hasExpired(lastRecordedTime)).thenCallRealMethod(); - - // WHEN - boolean hasExpired = integrationServiceMock.hasExpired(lastRecordedTime); - - // THEN - assertThat(hasExpired).isTrue(); - } - - private static Stream provideTestParamsForHasExpiredTrue() { - return Stream.of( - Arguments.of(10L, 0L, 9L), - Arguments.of(10L, 7L, 2L), - Arguments.of(10L, 8L, 1L), - Arguments.of(10000L, 5000L, 3000L) - ); - } - - @ParameterizedTest - @MethodSource("provideTestParamsForHasExpiredFalse") - public void givenNotExpiredLastRecordedTime_whenCheckingForExpiry_thenShouldReturnFalse(long currentTimeMillis, long lastRecordedTime, long reportingPeriodMillis) { - // GIVEN - ReflectionTestUtils.setField(integrationServiceMock, "reportingPeriodMillis", reportingPeriodMillis); - - when(integrationServiceMock.getCurrentTimeMillis()).thenReturn(currentTimeMillis); - when(integrationServiceMock.hasExpired(lastRecordedTime)).thenCallRealMethod(); - - // WHEN - boolean hasExpired = integrationServiceMock.hasExpired(lastRecordedTime); - - // THEN - assertThat(hasExpired).isFalse(); - } - - private static Stream provideTestParamsForHasExpiredFalse() { - return Stream.of( - Arguments.of(10L, 9L, 2L), - Arguments.of(10L, 0L, 11L), - Arguments.of(10L, 8L, 3L), - Arguments.of(10000L, 8000L, 3000L) - ); - } - - @Test - void givenKeyAndMetadata_whenOnStateExpiryCalled_thenShouldDoNothing() { - // GIVEN - var key = new IntegrationActivityKey(TENANT_ID, DEVICE_ID); - doCallRealMethod().when(integrationServiceMock).onStateExpiry(key, null); - - // WHEN-THEN - assertThatNoException().isThrownBy(() -> integrationServiceMock.onStateExpiry(key, null)); - } - -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java index 89efec8e1c..84d6d37824 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/AbstractActivityManager.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java index 12eae33ee7..0f2145ab6f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityManager.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java index 2b00495ed2..c58f2bfa34 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityReportCallback.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java index b470a52d9d..bde5e05645 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/ActivityState.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2023 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java index 497ed31f9d..b8fbb9ff12 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategy.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java index 4db053f84a..3f2ec74c3c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyType.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java index 6fdbca31ec..f7d2679689 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategy.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java index 4b8234ac9b..a5a59046ae 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategy.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java index 3e75154c3e..5353635c45 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategy.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java index b731aa275e..bce35b0b34 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategy.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java index 89232623f6..7dcf0e8fd2 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/ActivityStrategyTypeTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java index 2d550991a3..ea89b28a02 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/AllEventsActivityStrategyTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java index a1cb7ac302..9ca6b58502 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstAndLastEventActivityStrategyTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java index 585c9caeb1..0df3517098 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/FirstEventActivityStrategyTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java index 441a88c32f..d747c13787 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/activity/strategy/LastEventActivityStrategyTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.activity.strategy; diff --git a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java index 30395f28f4..e0f5ef128e 100644 --- a/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java +++ b/common/transport/transport-api/src/test/java/org/thingsboard/server/common/transport/service/TransportActivityManagerTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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; From 1a0ea09dea9757b8ace5254db876f9a1e3b916d6 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 11 Jan 2024 16:42:29 +0200 Subject: [PATCH 29/31] Add missed import during cherry picking --- .../common/transport/service/DefaultTransportService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index dc58c79250..83928759b0 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -23,6 +23,7 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; @@ -49,6 +50,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.limit.LimitedApi; +import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsTrigger; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rpc.RpcStatus; From d23411f8d2766be115e6ac57909a8577982ddfca Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 11 Jan 2024 18:39:26 +0200 Subject: [PATCH 30/31] Refactor: move activity-related logic in transport to abstract TransportActivityManager. DefaultTransportService now extends TransportActivityManager. --- .../service/DefaultTransportService.java | 119 +------------ .../service/TransportActivityManager.java | 164 ++++++++++++++++++ 2 files changed, 168 insertions(+), 115 deletions(-) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 83928759b0..6b5780002d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -73,11 +73,6 @@ import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportTenantProfileCache; -import org.thingsboard.server.common.transport.activity.AbstractActivityManager; -import org.thingsboard.server.common.transport.activity.ActivityReportCallback; -import org.thingsboard.server.common.transport.activity.ActivityState; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; -import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyType; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; @@ -121,7 +116,6 @@ import java.util.Optional; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -136,14 +130,12 @@ import java.util.stream.Collectors; @Slf4j @Service @TbTransportComponent -public class DefaultTransportService extends AbstractActivityManager implements TransportService { +public class DefaultTransportService extends TransportActivityManager implements TransportService { public static final String OVERWRITE_ACTIVITY_TIME = "overwriteActivityTime"; - public static final String SESSION_EXPIRED_MESSAGE = "Session has expired due to last activity time!"; - public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_OPEN = getSessionEventMsg(TransportProtos.SessionEvent.OPEN); - public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_CLOSED = getSessionEventMsg(TransportProtos.SessionEvent.CLOSED); - public static final TransportProtos.SessionCloseNotificationProto SESSION_EXPIRED_NOTIFICATION_PROTO = TransportProtos.SessionCloseNotificationProto.newBuilder() - .setMessage(SESSION_EXPIRED_MESSAGE).build(); + public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_OPEN = TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(TransportProtos.SessionEvent.OPEN).build(); public static final TransportProtos.SubscribeToAttributeUpdatesMsg SUBSCRIBE_TO_ATTRIBUTE_UPDATES_ASYNC_MSG = TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder() .setSessionType(TransportProtos.SessionType.ASYNC).build(); public static final TransportProtos.SubscribeToRPCMsg SUBSCRIBE_TO_RPC_ASYNC_MSG = TransportProtos.SubscribeToRPCMsg.newBuilder() @@ -155,12 +147,6 @@ public class DefaultTransportService extends AbstractActivityManager sessions = new ConcurrentHashMap<>(); private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); private volatile boolean stopped = false; @@ -790,100 +775,10 @@ public class DefaultTransportService extends AbstractActivityManager createNewState(UUID sessionId) { - SessionMetaData session = sessions.get(sessionId); - if (session == null) { - return null; - } - ActivityState state = new ActivityState<>(); - state.setMetadata(session.getSessionInfo()); - return state; - } - - @Override - protected ActivityStrategy getStrategy() { - return reportingStrategyType.toStrategy(); - } - - @Override - protected ActivityState updateState(UUID sessionId, ActivityState state) { - SessionMetaData session = sessions.get(sessionId); - if (session == null) { - return null; - } - - state.setMetadata(session.getSessionInfo()); - var sessionInfo = state.getMetadata(); - - if (sessionInfo.getGwSessionIdMSB() == 0L || sessionInfo.getGwSessionIdLSB() == 0L) { - return state; - } - - var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); - SessionMetaData gwSession = sessions.get(gwSessionId); - if (gwSession == null || !gwSession.isOverwriteActivityTime()) { - return state; - } - - long lastRecordedTime = state.getLastRecordedTime(); - long gwLastRecordedTime = getLastRecordedTime(gwSessionId); - log.debug("Session with id: [{}] has gateway session with id: [{}] with overwrite activity time enabled. " + - "Updating last activity time. Session last recorded time: [{}], gateway session last recorded time: [{}].", - sessionId, gwSessionId, lastRecordedTime, gwLastRecordedTime); - state.setLastRecordedTime(Math.max(lastRecordedTime, gwLastRecordedTime)); - return state; - } - - @Override - protected boolean hasExpired(long lastRecordedTime) { - return (getCurrentTimeMillis() - sessionInactivityTimeout) > lastRecordedTime; - } - long getCurrentTimeMillis() { return System.currentTimeMillis(); } - @Override - protected void onStateExpiry(UUID sessionId, TransportProtos.SessionInfoProto sessionInfo) { - log.debug("Session with id: [{}] has expired due to last activity time.", sessionId); - SessionMetaData expiredSession = sessions.remove(sessionId); - if (expiredSession != null) { - deregisterSession(sessionInfo); - process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); - expiredSession.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); - } - } - - @Override - protected void reportActivity(UUID sessionId, TransportProtos.SessionInfoProto currentSessionInfo, long timeToReport, ActivityReportCallback callback) { - log.debug("Reporting activity state for session with id: [{}]. Time to report: [{}].", sessionId, timeToReport); - SessionMetaData session = sessions.get(sessionId); - TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(session != null && session.isSubscribedToAttributes()) - .setRpcSubscription(session != null && session.isSubscribedToRPC()) - .setLastActivityTime(timeToReport) - .build(); - TransportProtos.SessionInfoProto sessionInfo = session != null ? session.getSessionInfo() : currentSessionInfo; - process(sessionInfo, subscriptionInfo, new TransportServiceCallback<>() { - @Override - public void onSuccess(Void msgAcknowledged) { - callback.onSuccess(sessionId, timeToReport); - - } - - @Override - public void onError(Throwable e) { - callback.onFailure(sessionId, e); - } - }); - } - @Override public void lifecycleEvent(TenantId tenantId, DeviceId deviceId, ComponentLifecycleEvent eventType, boolean success, Throwable error) { ToCoreMsg msg = ToCoreMsg.newBuilder() @@ -1199,12 +1094,6 @@ public class DefaultTransportService extends AbstractActivityManager callback) { ToCoreMsg toCoreMsg = ToCoreMsg.newBuilder().setToDeviceActorMsg(toDeviceActorMsg).build(); sendToCore(getTenantId(sessionInfo), getDeviceId(sessionInfo), toCoreMsg, getRoutingKey(sessionInfo), callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java new file mode 100644 index 0000000000..0c2a03e49f --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java @@ -0,0 +1,164 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.common.transport.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.activity.AbstractActivityManager; +import org.thingsboard.server.common.transport.activity.ActivityReportCallback; +import org.thingsboard.server.common.transport.activity.ActivityState; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategy; +import org.thingsboard.server.common.transport.activity.strategy.ActivityStrategyType; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Slf4j +public abstract class TransportActivityManager extends AbstractActivityManager implements TransportService { + + public static final String SESSION_EXPIRED_MESSAGE = "Session has expired due to last activity time!"; + + public static final TransportProtos.SessionEventMsg SESSION_EVENT_MSG_CLOSED = TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(TransportProtos.SessionEvent.CLOSED).build(); + public static final TransportProtos.SessionCloseNotificationProto SESSION_EXPIRED_NOTIFICATION_PROTO = TransportProtos.SessionCloseNotificationProto.newBuilder() + .setMessage(SESSION_EXPIRED_MESSAGE).build(); + + public final ConcurrentMap sessions = new ConcurrentHashMap<>(); + + @Value("${transport.sessions.report_timeout}") + protected long sessionReportTimeout; + + @Value("${transport.sessions.inactivity_timeout}") + protected long sessionInactivityTimeout; + + @Value("${transport.activity.reporting_strategy:LAST}") + private ActivityStrategyType reportingStrategyType; + + @Override + protected long getReportingPeriodMillis() { + return sessionReportTimeout; + } + + @Override + protected ActivityState createNewState(UUID sessionId) { + SessionMetaData session = sessions.get(sessionId); + if (session == null) { + return null; + } + ActivityState state = new ActivityState<>(); + state.setMetadata(session.getSessionInfo()); + return state; + } + + @Override + protected ActivityStrategy getStrategy() { + return reportingStrategyType.toStrategy(); + } + + @Override + protected ActivityState updateState(UUID sessionId, ActivityState state) { + SessionMetaData session = sessions.get(sessionId); + if (session == null) { + return null; + } + + state.setMetadata(session.getSessionInfo()); + var sessionInfo = state.getMetadata(); + + if (sessionInfo.getGwSessionIdMSB() == 0L || sessionInfo.getGwSessionIdLSB() == 0L) { + return state; + } + + var gwSessionId = new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB()); + SessionMetaData gwSession = sessions.get(gwSessionId); + if (gwSession == null || !gwSession.isOverwriteActivityTime()) { + return state; + } + + long lastRecordedTime = state.getLastRecordedTime(); + long gwLastRecordedTime = getLastRecordedTime(gwSessionId); + log.debug("Session with id: [{}] has gateway session with id: [{}] with overwrite activity time enabled. " + + "Updating last activity time. Session last recorded time: [{}], gateway session last recorded time: [{}].", + sessionId, gwSessionId, lastRecordedTime, gwLastRecordedTime); + state.setLastRecordedTime(Math.max(lastRecordedTime, gwLastRecordedTime)); + return state; + } + + @Override + protected boolean hasExpired(long lastRecordedTime) { + return (getCurrentTimeMillis() - sessionInactivityTimeout) > lastRecordedTime; + } + + @Override + protected void onStateExpiry(UUID sessionId, TransportProtos.SessionInfoProto sessionInfo) { + log.debug("Session with id: [{}] has expired due to last activity time.", sessionId); + SessionMetaData expiredSession = sessions.remove(sessionId); + if (expiredSession != null) { + deregisterSession(sessionInfo); + process(sessionInfo, SESSION_EVENT_MSG_CLOSED, null); + expiredSession.getListener().onRemoteSessionCloseCommand(sessionId, SESSION_EXPIRED_NOTIFICATION_PROTO); + } + } + + @Override + protected void reportActivity(UUID sessionId, TransportProtos.SessionInfoProto currentSessionInfo, long timeToReport, ActivityReportCallback callback) { + log.debug("Reporting activity state for session with id: [{}]. Time to report: [{}].", sessionId, timeToReport); + SessionMetaData session = sessions.get(sessionId); + TransportProtos.SubscriptionInfoProto subscriptionInfo = TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(session != null && session.isSubscribedToAttributes()) + .setRpcSubscription(session != null && session.isSubscribedToRPC()) + .setLastActivityTime(timeToReport) + .build(); + TransportProtos.SessionInfoProto sessionInfo = session != null ? session.getSessionInfo() : currentSessionInfo; + process(sessionInfo, subscriptionInfo, new TransportServiceCallback<>() { + @Override + public void onSuccess(Void msgAcknowledged) { + callback.onSuccess(sessionId, timeToReport); + + } + + @Override + public void onError(Throwable e) { + callback.onFailure(sessionId, e); + } + }); + } + + long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } + +} From 8acbf85e9f6b03e4c549649fafb33cb963f900c0 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 11 Jan 2024 19:10:58 +0200 Subject: [PATCH 31/31] Fix license --- .../service/TransportActivityManager.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java index 0c2a03e49f..c9b1859de6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportActivityManager.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2024 The Thingsboard Authors * - * Copyright © 2016-2024 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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;