diff --git a/application/pom.xml b/application/pom.xml index 3093dcfb57..f76a0709c7 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -354,6 +354,10 @@ com.slack.api slack-api-client + + com.google.firebase + firebase-admin + diff --git a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java index e586f079ca..86131b41af 100644 --- a/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java +++ b/application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java @@ -149,7 +149,7 @@ public class SystemInfoController extends BaseController { infoObject.put("artifact", buildProperties.getArtifact()); infoObject.put("name", buildProperties.getName()); } else { - infoObject.put("version", "unknown"); + infoObject.put("version", "3.5.5"); } return infoObject; } 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 new file mode 100644 index 0000000000..75ff7866ab --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/notification/channels/MobileNotificationChannel.java @@ -0,0 +1,109 @@ +/** + * 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.channels; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +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.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; +import org.thingsboard.server.common.data.notification.settings.MobileNotificationDeliveryMethodConfig; +import org.thingsboard.server.common.data.notification.settings.NotificationSettings; +import org.thingsboard.server.common.data.notification.template.MobileDeliveryMethodNotificationTemplate; +import org.thingsboard.server.dao.notification.NotificationSettingsService; +import org.thingsboard.server.service.executors.ExternalCallExecutorService; +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 ExternalCallExecutorService executor; + private final NotificationSettingsService notificationSettingsService; + + @Override + public ListenableFuture sendNotification(User recipient, MobileDeliveryMethodNotificationTemplate processedTemplate, NotificationProcessingContext ctx) { + String fcmToken = Optional.ofNullable(recipient.getAdditionalInfo()) + .map(info -> info.get("fcmToken")).filter(JsonNode::isTextual).map(JsonNode::asText) + .orElse(null); + if (StringUtils.isEmpty(fcmToken)) { + return Futures.immediateFailedFuture(new RuntimeException("User doesn't have the mobile app installed")); + } + + MobileNotificationDeliveryMethodConfig config = ctx.getDeliveryMethodConfig(NotificationDeliveryMethod.MOBILE); + return executor.submit(() -> { + FirebaseOptions firebaseOptions = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.fromStream(IOUtils.toInputStream(config.getFirebaseServiceAccountCredentials(), StandardCharsets.UTF_8))) + .build(); + String appName = ctx.getTenantId().toString(); + + FirebaseApp firebaseApp = FirebaseApp.getApps().stream() + .filter(app -> 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); + return null; + }); + } + + @Override + public void check(TenantId tenantId) throws Exception { + NotificationSettings settings = notificationSettingsService.findNotificationSettings(tenantId); + if (!settings.getDeliveryMethodsConfigs().containsKey(NotificationDeliveryMethod.MOBILE)) { + throw new RuntimeException("Push-notifications to mobile are not configured"); + } + } + + @Override + public NotificationDeliveryMethod getDeliveryMethod() { + return NotificationDeliveryMethod.MOBILE; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java index 4a2c4657d5..5b9e84d37d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/NotificationDeliveryMethod.java @@ -24,7 +24,8 @@ public enum NotificationDeliveryMethod { WEB("web"), EMAIL("email"), SMS("SMS"), - SLACK("Slack"); + SLACK("Slack"), + MOBILE("mobile"); @Getter private final String name; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/MobileNotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/MobileNotificationDeliveryMethodConfig.java new file mode 100644 index 0000000000..4e443227ee --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/MobileNotificationDeliveryMethodConfig.java @@ -0,0 +1,35 @@ +/** + * 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.common.data.notification.settings; + +import lombok.Data; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +import javax.validation.constraints.NotEmpty; + +@Data +public class MobileNotificationDeliveryMethodConfig implements NotificationDeliveryMethodConfig { + + private String firebaseServiceAccountCredentialsFileName; + @NotEmpty + private String firebaseServiceAccountCredentials; + + @Override + public NotificationDeliveryMethod getMethod() { + return NotificationDeliveryMethod.MOBILE; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java index 962eba6d06..28020d6c4e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/settings/NotificationDeliveryMethodConfig.java @@ -27,7 +27,8 @@ import java.io.Serializable; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "method") @JsonSubTypes({ - @Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class) + @Type(name = "SLACK", value = SlackNotificationDeliveryMethodConfig.class), + @Type(name = "MOBILE", value = MobileNotificationDeliveryMethodConfig.class) }) public interface NotificationDeliveryMethodConfig extends Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java index 1254654ecf..2180679cb2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/targets/NotificationTargetType.java @@ -25,7 +25,7 @@ import java.util.Set; @RequiredArgsConstructor public enum NotificationTargetType { - PLATFORM_USERS(Set.of(NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS)), + PLATFORM_USERS(Set.of(NotificationDeliveryMethod.WEB, NotificationDeliveryMethod.EMAIL, NotificationDeliveryMethod.SMS, NotificationDeliveryMethod.MOBILE)), SLACK(Set.of(NotificationDeliveryMethod.SLACK)); @Getter diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java index ecb7f2d2b6..f033fe5092 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/DeliveryMethodNotificationTemplate.java @@ -33,7 +33,8 @@ import javax.validation.constraints.NotEmpty; @Type(name = "WEB", value = WebDeliveryMethodNotificationTemplate.class), @Type(name = "EMAIL", value = EmailDeliveryMethodNotificationTemplate.class), @Type(name = "SMS", value = SmsDeliveryMethodNotificationTemplate.class), - @Type(name = "SLACK", value = SlackDeliveryMethodNotificationTemplate.class) + @Type(name = "SLACK", value = SlackDeliveryMethodNotificationTemplate.class), + @Type(name = "MOBILE", value = MobileDeliveryMethodNotificationTemplate.class) }) @Data @NoArgsConstructor diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/MobileDeliveryMethodNotificationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/MobileDeliveryMethodNotificationTemplate.java new file mode 100644 index 0000000000..91786db48a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/template/MobileDeliveryMethodNotificationTemplate.java @@ -0,0 +1,56 @@ +/** + * 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.common.data.notification.template; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod; + +import javax.validation.constraints.NotEmpty; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class MobileDeliveryMethodNotificationTemplate extends DeliveryMethodNotificationTemplate implements HasSubject { + + @NotEmpty + private String subject; + + public MobileDeliveryMethodNotificationTemplate(MobileDeliveryMethodNotificationTemplate other) { + super(other); + this.subject = other.subject; + } + + @Override + public NotificationDeliveryMethod getMethod() { + return NotificationDeliveryMethod.MOBILE; + } + + @Override + public MobileDeliveryMethodNotificationTemplate copy() { + return new MobileDeliveryMethodNotificationTemplate(this); + } + + @Override + public boolean containsAny(String... params) { + return super.containsAny(params) || StringUtils.containsAny(subject, params); + } + +} diff --git a/pom.xml b/pom.xml index f78a4826bc..f58e80480a 100755 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,7 @@ 2.21.0 2.12.0 1.12.1 + 8.0.1 3.4.0 @@ -1987,6 +1988,11 @@ slack-api-client ${slack-api.version} + + com.google.firebase + firebase-admin + ${firebase-admin.version} + org.eclipse.jgit org.eclipse.jgit