diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index bcf601ab8..7348d52e2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -58,6 +58,8 @@ public class FineractProperties { private FineractQueryProperties query; private FineractApiProperties api; + private FineractNotificationProperties notification; + @Getter @Setter public static class FineractTenantProperties { @@ -280,4 +282,17 @@ public class FineractProperties { private int inlineLoanCob; } + @Getter + @Setter + public static class FineractNotificationProperties { + + private UserNotificationSystemProperties userNotificationSystem; + } + + @Getter + @Setter + public static class UserNotificationSystemProperties { + + private boolean enabled; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java index d9a43c437..3709ec45d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/filter/TenantAwareBasicAuthenticationFilter.java @@ -37,7 +37,7 @@ import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.security.data.PlatformRequestLog; import org.apache.fineract.infrastructure.security.exception.InvalidTenantIdentifierException; import org.apache.fineract.infrastructure.security.service.BasicAuthTenantDetailsService; -import org.apache.fineract.notification.service.NotificationReadPlatformService; +import org.apache.fineract.notification.service.UserNotificationService; import org.apache.fineract.useradministration.domain.AppUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,7 +79,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil private CacheWritePlatformService cacheWritePlatformService; @Autowired - private NotificationReadPlatformService notificationReadPlatformService; + private UserNotificationService userNotificationService; @Autowired private BasicAuthTenantDetailsService basicAuthTenantDetailsService; @@ -170,7 +170,7 @@ public class TenantAwareBasicAuthenticationFilter extends BasicAuthenticationFil super.onSuccessfulAuthentication(request, response, authResult); AppUser user = (AppUser) authResult.getPrincipal(); - if (notificationReadPlatformService.hasUnreadNotifications(user.getId())) { + if (userNotificationService.hasUnreadUserNotifications(user.getId())) { response.addHeader("X-Notification-Refresh", "true"); } else { response.addHeader("X-Notification-Refresh", "false"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java index 31dc3cbe9..75eb78f58 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/eventandlistener/NotificationEventListener.java @@ -18,46 +18,19 @@ */ package org.apache.fineract.notification.eventandlistener; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; import lombok.RequiredArgsConstructor; import org.apache.fineract.notification.data.NotificationData; -import org.apache.fineract.notification.service.NotificationWritePlatformService; -import org.apache.fineract.useradministration.domain.AppUser; -import org.apache.fineract.useradministration.domain.AppUserRepository; +import org.apache.fineract.notification.service.UserNotificationService; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class NotificationEventListener { - private final NotificationWritePlatformService notificationWritePlatformService; - private final AppUserRepository appUserRepository; + private final UserNotificationService userNotificationService; public void receive(NotificationData notificationData) { - Long appUserId = notificationData.getActorId(); + userNotificationService.notifyUsers(notificationData); - Set userIds = notificationData.getUserIds(); - - if (notificationData.getOfficeId() != null) { - List tempUserIds = new ArrayList<>(userIds); - for (Long userId : tempUserIds) { - AppUser appUser = appUserRepository.findById(userId).orElseThrow(); - if (!Objects.equals(appUser.getOffice().getId(), notificationData.getOfficeId())) { - userIds.remove(userId); - } - } - } - - // Don't notify the same user who triggered the event - if (userIds.contains(appUserId)) { - userIds.remove(appUserId); - } - - notificationWritePlatformService.notify(userIds, notificationData.getObjectType(), notificationData.getObjectId(), - notificationData.getAction(), notificationData.getActorId(), notificationData.getContent(), - notificationData.isSystemGenerated()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java index 1fcb893a7..750452359 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationDomainServiceImpl.java @@ -18,15 +18,10 @@ */ package org.apache.fineract.notification.service; -import static java.util.stream.Collectors.toSet; - -import java.util.Collection; -import java.util.Set; import javax.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; -import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.event.business.BusinessEventListener; import org.apache.fineract.infrastructure.event.business.domain.client.ClientCreateBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.deposit.FixedDepositAccountCreateBusinessEvent; @@ -50,8 +45,6 @@ import org.apache.fineract.infrastructure.event.business.domain.share.ShareAccou import org.apache.fineract.infrastructure.event.business.domain.share.ShareProductDividentsCreateBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; -import org.apache.fineract.notification.data.NotificationData; -import org.apache.fineract.notification.eventandlistener.NotificationEventPublisher; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -62,8 +55,6 @@ import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransaction; import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount; -import org.apache.fineract.useradministration.domain.AppUser; -import org.apache.fineract.useradministration.domain.AppUserRepository; import org.springframework.stereotype.Service; @Service @@ -73,8 +64,7 @@ public class NotificationDomainServiceImpl implements NotificationDomainService private final BusinessEventNotifierService businessEventNotifierService; private final PlatformSecurityContext context; - private final NotificationEventPublisher notificationEventPublisher; - private final AppUserRepository appUserRepository; + private final UserNotificationService userNotificationService; @PostConstruct public void addListeners() { @@ -327,22 +317,7 @@ public class NotificationDomainServiceImpl implements NotificationDomainService private void buildNotification(String permission, String objectType, Long objectIdentifier, String notificationContent, String eventType, Long appUserId, Long officeId) { - String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); - Set userIds = getNotifiableUserIds(officeId, permission); - NotificationData notificationData = new NotificationData().setObjectType(objectType).setObjectId(objectIdentifier) - .setAction(eventType).setActorId(appUserId).setContent(notificationContent).setRead(false).setSystemGenerated(false) - .setTenantIdentifier(tenantIdentifier).setOfficeId(officeId).setUserIds(userIds); - try { - notificationEventPublisher.broadcastNotification(notificationData); - } catch (Exception e) { - // We want to avoid rethrowing the exception to stop the business transaction from rolling back - log.error("Error while broadcasting notification event", e); - } + userNotificationService.notifyUsers(permission, objectType, objectIdentifier, notificationContent, eventType, appUserId, officeId); } - private Set getNotifiableUserIds(Long officeId, String permission) { - Collection users = appUserRepository.findByOfficeId(officeId); - Collection usersWithPermission = users.stream().filter(aU -> aU.hasAnyPermission(permission, "ALL_FUNCTIONS")).toList(); - return usersWithPermission.stream().map(AppUser::getId).collect(toSet()); - } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java index d0999baf6..65a38804c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformService.java @@ -22,9 +22,6 @@ import java.util.Collection; public interface NotificationWritePlatformService { - Long notify(Long userId, String objectType, Long objectId, String action, Long actorId, String notificationContent, - boolean isSystemGenerated); - Long notify(Collection userIds, String objectType, Long objectId, String action, Long actorId, String notificationContent, boolean isSystemGenerated); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java index 42105d9cc..e5a5b68f4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/NotificationWritePlatformServiceImpl.java @@ -40,26 +40,6 @@ public class NotificationWritePlatformServiceImpl implements NotificationWritePl private final AppUserRepository appUserRepository; private final NotificationMapperWritePlatformService notificationMapperWritePlatformService; - @Override - public Long notify(Long userId, String objectType, Long objectIdentifier, String action, Long actorId, String notificationContent, - boolean isSystemGenerated) { - - Long generatedNotificationId = insertIntoNotificationGenerator(objectType, objectIdentifier, action, actorId, notificationContent, - isSystemGenerated); - insertIntoNotificationMapper(userId, generatedNotificationId); - return generatedNotificationId; - } - - private Long insertIntoNotificationMapper(Long userId, Long generatedNotificationId) { - AppUser appUser = this.appUserRepository.findById(userId).orElse(null); - NotificationMapper notificationMapper = new NotificationMapper() - .setNotification(this.notificationGeneratorReadRepositoryWrapper.findById(generatedNotificationId)).setUserId(appUser) - .setRead(false).setCreatedAt(DateUtils.getLocalDateTimeOfSystem()); - - this.notificationMapperWritePlatformService.create(notificationMapper); - return notificationMapper.getId(); - } - private Long insertIntoNotificationGenerator(String objectType, Long objectIdentifier, String action, Long actorId, String notificationContent, boolean isSystemGenerated) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationService.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationService.java new file mode 100644 index 000000000..06b9a6405 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationService.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.notification.service; + +import org.apache.fineract.notification.data.NotificationData; + +public interface UserNotificationService { + + void notifyUsers(String permission, String objectType, Long objectIdentifier, String notificationContent, String eventType, + Long appUserId, Long officeId); + + boolean hasUnreadUserNotifications(Long appUserId); + + void notifyUsers(NotificationData notificationData); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationServiceImpl.java new file mode 100644 index 000000000..f597aa4a1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/notification/service/UserNotificationServiceImpl.java @@ -0,0 +1,114 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.fineract.notification.service; + +import static java.util.stream.Collectors.toSet; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.config.FineractProperties; +import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; +import org.apache.fineract.notification.data.NotificationData; +import org.apache.fineract.notification.eventandlistener.NotificationEventPublisher; +import org.apache.fineract.useradministration.domain.AppUser; +import org.apache.fineract.useradministration.domain.AppUserRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class UserNotificationServiceImpl implements UserNotificationService { + + private final NotificationEventPublisher notificationEventPublisher; + private final AppUserRepository appUserRepository; + private final FineractProperties fineractProperties; + private final NotificationReadPlatformService notificationReadPlatformService; + private final NotificationWritePlatformService notificationWritePlatformService; + + @Override + public void notifyUsers(String permission, String objectType, Long objectIdentifier, String notificationContent, String eventType, + Long appUserId, Long officeId) { + + if (userNotificationSystemIsEnabled()) { + String tenantIdentifier = ThreadLocalContextUtil.getTenant().getTenantIdentifier(); + Set userIds = getNotifiableUserIds(officeId, permission); + NotificationData notificationData = new NotificationData().setObjectType(objectType).setObjectId(objectIdentifier) + .setAction(eventType).setActorId(appUserId).setContent(notificationContent).setRead(false).setSystemGenerated(false) + .setTenantIdentifier(tenantIdentifier).setOfficeId(officeId).setUserIds(userIds); + try { + notificationEventPublisher.broadcastNotification(notificationData); + } catch (Exception e) { + // We want to avoid rethrowing the exception to stop the business transaction from rolling back + log.error("Error while broadcasting notification event", e); + } + } + } + + @Override + public boolean hasUnreadUserNotifications(Long appUserId) { + if (userNotificationSystemIsEnabled()) { + return notificationReadPlatformService.hasUnreadNotifications(appUserId); + } else { + return false; + } + } + + @Override + public void notifyUsers(NotificationData notificationData) { + if (userNotificationSystemIsEnabled()) { + Long appUserId = notificationData.getActorId(); + + Set userIds = notificationData.getUserIds(); + + if (notificationData.getOfficeId() != null) { + List tempUserIds = new ArrayList<>(userIds); + for (Long userId : tempUserIds) { + AppUser appUser = appUserRepository.findById(userId).orElseThrow(); + if (!Objects.equals(appUser.getOffice().getId(), notificationData.getOfficeId())) { + userIds.remove(userId); + } + } + } + + // Don't notify the same user who triggered the event + if (userIds.contains(appUserId)) { + userIds.remove(appUserId); + } + + notificationWritePlatformService.notify(userIds, notificationData.getObjectType(), notificationData.getObjectId(), + notificationData.getAction(), notificationData.getActorId(), notificationData.getContent(), + notificationData.isSystemGenerated()); + } + } + + private boolean userNotificationSystemIsEnabled() { + return fineractProperties.getNotification().getUserNotificationSystem().isEnabled(); + } + + private Set getNotifiableUserIds(Long officeId, String permission) { + Collection users = appUserRepository.findByOfficeId(officeId); + Collection usersWithPermission = users.stream().filter(aU -> aU.hasAnyPermission(permission, "ALL_FUNCTIONS")).toList(); + return usersWithPermission.stream().map(AppUser::getId).collect(toSet()); + } +} diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index 714e20715..195496ba0 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -102,6 +102,8 @@ fineract.report.export.s3.enabled=${FINERACT_REPORT_EXPORT_S3_ENABLED:false} fineract.jpa.statementLoggingEnabled=${FINERACT_STATEMENT_LOGGING_ENABLED:false} +fineract.notification.user-notification-system.enabled=${FINERACT_USER_NOTIFICATION_SYSTEM_ENABLED:true} + # Logging pattern for the console logging.pattern.console=${CONSOLE_LOG_PATTERN:%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(%replace([%X{correlationId}]){'\\[\\]', ''}) %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}