| /** |
| * 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 java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.HashMap; |
| import java.util.List; |
| import lombok.RequiredArgsConstructor; |
| import org.apache.fineract.infrastructure.core.service.Page; |
| import org.apache.fineract.infrastructure.core.service.PaginationHelper; |
| import org.apache.fineract.infrastructure.core.service.SearchParameters; |
| import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; |
| import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; |
| import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; |
| import org.apache.fineract.infrastructure.security.utils.ColumnValidator; |
| import org.apache.fineract.notification.cache.CacheNotificationResponseHeader; |
| import org.apache.fineract.notification.data.NotificationData; |
| import org.apache.fineract.notification.data.NotificationMapperData; |
| import org.springframework.jdbc.core.JdbcTemplate; |
| import org.springframework.jdbc.core.RowMapper; |
| |
| @RequiredArgsConstructor |
| public class NotificationReadPlatformServiceImpl implements NotificationReadPlatformService { |
| |
| private HashMap<Long, HashMap<Long, CacheNotificationResponseHeader>> tenantNotificationResponseHeaderCache = new HashMap<>(); |
| |
| private final NotificationDataRow notificationDataRow = new NotificationDataRow(); |
| private final NotificationMapperRow notificationMapperRow = new NotificationMapperRow(); |
| |
| private final JdbcTemplate jdbcTemplate; |
| private final PlatformSecurityContext context; |
| private final ColumnValidator columnValidator; |
| private final PaginationHelper paginationHelper; |
| private final DatabaseSpecificSQLGenerator sqlGenerator; |
| |
| @Override |
| public boolean hasUnreadNotifications(Long appUserId) { |
| Long tenantId = ThreadLocalContextUtil.getTenant().getId(); |
| Long now = System.currentTimeMillis() / 1000L; |
| if (this.tenantNotificationResponseHeaderCache.containsKey(tenantId)) { |
| HashMap<Long, CacheNotificationResponseHeader> notificationResponseHeaderCache = this.tenantNotificationResponseHeaderCache |
| .get(tenantId); |
| if (notificationResponseHeaderCache.containsKey(appUserId)) { |
| Long lastFetch = notificationResponseHeaderCache.get(appUserId).getLastFetch(); |
| if ((now - lastFetch) > 1) { |
| return this.createUpdateCacheValue(appUserId, now, notificationResponseHeaderCache); |
| } else { |
| return notificationResponseHeaderCache.get(appUserId).hasNotifications(); |
| } |
| } else { |
| return this.createUpdateCacheValue(appUserId, now, notificationResponseHeaderCache); |
| } |
| } else { |
| return this.initializeTenantNotificationResponseHeaderCache(tenantId, now, appUserId); |
| |
| } |
| } |
| |
| private boolean initializeTenantNotificationResponseHeaderCache(Long tenantId, Long now, Long appUserId) { |
| HashMap<Long, CacheNotificationResponseHeader> notificationResponseHeaderCache = new HashMap<>(); |
| this.tenantNotificationResponseHeaderCache.put(tenantId, notificationResponseHeaderCache); |
| return this.createUpdateCacheValue(appUserId, now, notificationResponseHeaderCache); |
| } |
| |
| private boolean createUpdateCacheValue(Long appUserId, Long now, |
| HashMap<Long, CacheNotificationResponseHeader> notificationResponseHeaderCache) { |
| boolean hasNotifications; |
| Long tenantId = ThreadLocalContextUtil.getTenant().getId(); |
| CacheNotificationResponseHeader cacheNotificationResponseHeader; |
| hasNotifications = checkForUnreadNotifications(appUserId); |
| cacheNotificationResponseHeader = new CacheNotificationResponseHeader(hasNotifications, now); |
| notificationResponseHeaderCache.put(appUserId, cacheNotificationResponseHeader); |
| this.tenantNotificationResponseHeaderCache.put(tenantId, notificationResponseHeaderCache); |
| return hasNotifications; |
| } |
| |
| private boolean checkForUnreadNotifications(Long appUserId) { |
| String sql = "SELECT id, notification_id as notificationId, user_id as userId, is_read as isRead, created_at " |
| + "as createdAt FROM notification_mapper WHERE user_id = ? AND is_read = false"; |
| List<NotificationMapperData> notificationMappers = this.jdbcTemplate.query(sql, notificationMapperRow, appUserId); |
| return notificationMappers.size() > 0; |
| } |
| |
| @Override |
| public void updateNotificationReadStatus() { |
| final Long appUserId = context.authenticatedUser().getId(); |
| String sql = "UPDATE notification_mapper SET is_read = true WHERE is_read = false and user_id = ?"; |
| this.jdbcTemplate.update(sql, appUserId); |
| } |
| |
| @Override |
| public Page<NotificationData> getAllUnreadNotifications(final SearchParameters searchParameters) { |
| final Long appUserId = context.authenticatedUser().getId(); |
| String sql = "SELECT " + sqlGenerator.calcFoundRows() + " ng.id as id, nm.user_id as userId, ng.object_type as objectType, " |
| + "ng.object_identifier as objectId, ng.actor as actor, ng." + sqlGenerator.escape("action") |
| + " as action, ng.notification_content " |
| + "as content, ng.is_system_generated as isSystemGenerated, nm.created_at as createdAt " |
| + "FROM notification_mapper nm INNER JOIN notification_generator ng ON nm.notification_id = ng.id " |
| + "WHERE nm.user_id = ? AND nm.is_read = false order by nm.created_at desc"; |
| |
| return getNotificationDataPage(searchParameters, appUserId, sql); |
| } |
| |
| @Override |
| public Page<NotificationData> getAllNotifications(SearchParameters searchParameters) { |
| final Long appUserId = context.authenticatedUser().getId(); |
| String sql = "SELECT " + sqlGenerator.calcFoundRows() + " ng.id as id, nm.user_id as userId, ng.object_type as objectType, " |
| + "ng.object_identifier as objectId, ng.actor as actor, ng." + sqlGenerator.escape("action") |
| + " as action, ng.notification_content " |
| + "as content, ng.is_system_generated as isSystemGenerated, nm.created_at as createdAt " |
| + "FROM notification_mapper nm INNER JOIN notification_generator ng ON nm.notification_id = ng.id " |
| + "WHERE nm.user_id = ? order by nm.created_at desc"; |
| |
| return getNotificationDataPage(searchParameters, appUserId, sql); |
| } |
| |
| private Page<NotificationData> getNotificationDataPage(SearchParameters searchParameters, Long appUserId, String sql) { |
| final StringBuilder sqlBuilder = new StringBuilder(200); |
| sqlBuilder.append(sql); |
| |
| if (searchParameters.hasOrderBy()) { |
| sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); |
| this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); |
| if (searchParameters.hasSortOrder()) { |
| sqlBuilder.append(' ').append(searchParameters.getSortOrder()); |
| this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); |
| } |
| } |
| |
| if (searchParameters.hasLimit()) { |
| sqlBuilder.append(" "); |
| if (searchParameters.hasOffset()) { |
| sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit(), searchParameters.getOffset())); |
| } else { |
| sqlBuilder.append(sqlGenerator.limit(searchParameters.getLimit())); |
| } |
| } |
| |
| Object[] params = new Object[] { appUserId }; |
| return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlBuilder.toString(), params, this.notificationDataRow); |
| } |
| |
| private static final class NotificationMapperRow implements RowMapper<NotificationMapperData> { |
| |
| @Override |
| public NotificationMapperData mapRow(ResultSet rs, int rowNum) throws SQLException { |
| NotificationMapperData notificationMapperData = new NotificationMapperData(); |
| |
| final Long id = rs.getLong("id"); |
| notificationMapperData.setId(id); |
| |
| final Long notificationId = rs.getLong("notificationId"); |
| notificationMapperData.setNotificationId(notificationId); |
| |
| final Long userId = rs.getLong("userId"); |
| notificationMapperData.setUserId(userId); |
| |
| final boolean isRead = rs.getBoolean("isRead"); |
| notificationMapperData.setRead(isRead); |
| |
| final String createdAt = rs.getString("createdAt"); |
| notificationMapperData.setCreatedAt(createdAt); |
| |
| return notificationMapperData; |
| } |
| } |
| |
| private static final class NotificationDataRow implements RowMapper<NotificationData> { |
| |
| @Override |
| public NotificationData mapRow(ResultSet rs, int rowNum) throws SQLException { |
| NotificationData notificationData = new NotificationData(); |
| |
| final Long id = rs.getLong("id"); |
| notificationData.setId(id); |
| |
| final String objectType = rs.getString("objectType"); |
| notificationData.setObjectType(objectType); |
| |
| final Long objectId = rs.getLong("objectId"); |
| notificationData.setObjectId(objectId); |
| |
| final Long actorId = rs.getLong("actor"); |
| notificationData.setActorId(actorId); |
| |
| final String action = rs.getString("action"); |
| notificationData.setAction(action); |
| |
| final String content = rs.getString("content"); |
| notificationData.setContent(content); |
| |
| final boolean isSystemGenerated = rs.getBoolean("isSystemGenerated"); |
| notificationData.setSystemGenerated(isSystemGenerated); |
| |
| final String createdAt = rs.getString("createdAt"); |
| notificationData.setCreatedAt(createdAt); |
| |
| return notificationData; |
| } |
| } |
| } |