From c5fcc7a1bcc4a206792027b5d2e818de42e22d65 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 15 Jun 2023 11:31:21 +0300 Subject: [PATCH] Firebase usage refactoring --- .../server/actors/ActorSystemContext.java | 3 +- .../actors/ruleChain/DefaultTbContext.java | 3 +- .../NotificationTemplateController.java | 2 +- .../channels/MobileNotificationChannel.java | 42 +----- .../channels/SlackNotificationChannel.java | 2 +- .../provider/DefaultFirebaseService.java | 124 ++++++++++++++++++ .../provider}/DefaultSlackService.java | 4 +- .../AbstractNotificationApiTest.java | 2 +- .../rule/engine/api/TbContext.java | 2 +- .../api/notification/FirebaseService.java | 24 ++++ .../{slack => notification}/SlackService.java | 2 +- 11 files changed, 161 insertions(+), 49 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java rename application/src/main/java/org/thingsboard/server/service/{slack => notification/provider}/DefaultSlackService.java (98%) create mode 100644 rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java rename rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/{slack => notification}/SlackService.java (95%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index e04c7052db..ce2c359ec1 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,7 +32,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.SmsService; -import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.script.api.tbel.TbelInvokeService; @@ -115,7 +115,6 @@ import org.thingsboard.server.service.transport.TbCoreToTransportService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 8b76924d96..bcf67e0f0d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.actors.ruleChain; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.channel.EventLoopGroup; @@ -35,7 +34,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbRelationTypes; -import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.util.TenantIdLoader; import org.thingsboard.server.actors.ActorSystemContext; diff --git a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java index ab171efacf..4d5e24f765 100644 --- a/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java +++ b/application/src/main/java/org/thingsboard/server/controller/NotificationTemplateController.java @@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.NotificationTemplateId; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileNotificationChannel.java index fe515f2218..135ecfe717 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileNotificationChannel.java @@ -16,16 +16,10 @@ package org.thingsboard.server.service.notification.channels; import com.fasterxml.jackson.databind.JsonNode; -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.Message; -import com.google.firebase.messaging.Notification; import lombok.RequiredArgsConstructor; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import org.thingsboard.rule.engine.api.notification.FirebaseService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; @@ -35,13 +29,13 @@ import org.thingsboard.server.common.data.notification.template.MobileDeliveryMe import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.service.notification.NotificationProcessingContext; -import java.nio.charset.StandardCharsets; import java.util.Optional; @Component @RequiredArgsConstructor public class MobileNotificationChannel implements NotificationChannel { + private final FirebaseService firebaseService; private final NotificationSettingsService notificationSettingsService; @Override @@ -54,36 +48,8 @@ public class MobileNotificationChannel implements NotificationChannel app.getName().equals(appName)) - .findFirst().orElseGet(() -> { - try { - return FirebaseApp.initializeApp(firebaseOptions, appName); - } catch (IllegalStateException e) { - return FirebaseApp.getInstance(appName); - } - }); - FirebaseMessaging firebaseMessaging; - try { - firebaseMessaging = FirebaseMessaging.getInstance(firebaseApp); - } catch (IllegalArgumentException e) { - // because of concurrency issues: FirebaseMessaging.getInstance lazily loads FirebaseMessagingService - firebaseMessaging = FirebaseMessaging.getInstance(firebaseApp); - } - - Message message = Message.builder() - .setNotification(Notification.builder() - .setTitle(processedTemplate.getSubject()) - .setBody(processedTemplate.getBody()) - .build()) - .setToken(fcmToken) - .build(); - firebaseMessaging.send(message); + firebaseService.sendMessage(ctx.getTenantId(), config.getFirebaseServiceAccountCredentials(), + fcmToken, processedTemplate.getSubject(), processedTemplate.getBody()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java index 25c6b9dcab..909913df19 100644 --- a/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/SlackNotificationChannel.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.notification.channels; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; diff --git a/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java b/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java new file mode 100644 index 0000000000..372f631526 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java @@ -0,0 +1,124 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.notification.provider; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalCause; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.FirebaseMessagingException; +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; +import lombok.Getter; +import org.apache.commons.io.IOUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.notification.FirebaseService; +import org.thingsboard.server.common.data.id.TenantId; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +@Service +public class DefaultFirebaseService implements FirebaseService { + + private final Cache contexts = Caffeine.newBuilder() + .expireAfterAccess(1, TimeUnit.DAYS) + .removalListener((key, context, cause) -> { + if (cause == RemovalCause.EXPIRED && context != null) { + context.destroy(); + } + }) + .build(); + + @Override + public void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body) { + FirebaseContext firebaseContext = contexts.asMap().compute(tenantId.toString(), (key, context) -> { + if (context == null) { + return new FirebaseContext(key, credentials); + } else { + context.check(credentials); + return context; + } + }); + + Message message = Message.builder() + .setNotification(Notification.builder() + .setTitle(title) + .setBody(body) + .build()) + .setToken(fcmToken) + .build(); + try { + firebaseContext.getMessaging().send(message); + } catch (FirebaseMessagingException e) { + throw new RuntimeException("Failed to send message via FCM: " + e.getMessage(), e); + } + } + + public static class FirebaseContext { + private final String key; + private String credentials; + private FirebaseApp app; + @Getter + private FirebaseMessaging messaging; + + public FirebaseContext(String key, String credentials) { + this.key = key; + this.credentials = credentials; + init(); + } + + private void init() { + FirebaseOptions options; + try { + options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(IOUtils.toInputStream(credentials, StandardCharsets.UTF_8))) + .build(); + } catch (IOException e) { + throw new RuntimeException("Failed to process service account credentials: " + e.getMessage(), e); + } + try { + app = FirebaseApp.initializeApp(options, key); + } catch (IllegalStateException alreadyExists) { // should never normally happen + app = FirebaseApp.getInstance(key); + } + try { + messaging = FirebaseMessaging.getInstance(app); + } catch (IllegalStateException alreadyExists) { // should never normally happen + messaging = FirebaseMessaging.getInstance(app); + } + } + + public void check(String credentials) { + if (!this.credentials.equals(credentials)) { + app.delete(); + this.credentials = credentials; + init(); + } + } + + public void destroy() { + app.delete(); + app = null; + messaging = null; + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java b/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultSlackService.java similarity index 98% rename from application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java rename to application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultSlackService.java index ee54a02620..38b6def47d 100644 --- a/application/src/main/java/org/thingsboard/server/service/slack/DefaultSlackService.java +++ b/application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultSlackService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.slack; +package org.thingsboard.server.service.notification.provider; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; @@ -29,7 +29,7 @@ import com.slack.api.methods.response.users.UsersListResponse; import com.slack.api.model.ConversationType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; import org.thingsboard.server.common.data.notification.settings.NotificationSettings; diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index f45882e416..eb46503c41 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -22,7 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.util.Pair; import org.thingsboard.rule.engine.api.MailService; -import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationTargetId; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 1561dac05e..0ad8fc5e0f 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -17,7 +17,7 @@ package org.thingsboard.rule.engine.api; import io.netty.channel.EventLoopGroup; import org.thingsboard.common.util.ListeningExecutor; -import org.thingsboard.rule.engine.api.slack.SlackService; +import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java new file mode 100644 index 0000000000..e0da1e9b66 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/FirebaseService.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2023 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.rule.engine.api.notification; + +import org.thingsboard.server.common.data.id.TenantId; + +public interface FirebaseService { + + void sendMessage(TenantId tenantId, String credentials, String fcmToken, String title, String body); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/SlackService.java similarity index 95% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/SlackService.java index 18411ade1d..d1128abad6 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/slack/SlackService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/notification/SlackService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api.slack; +package org.thingsboard.rule.engine.api.notification; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.notification.targets.slack.SlackConversation;