All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.infusers.core.stats.users.ActiveUserService Maven / Gradle / Ivy

There is a newer version: 2024.12.0008
Show newest version
package com.infusers.core.stats.users;

import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import com.google.gson.Gson;
import com.infusers.core.audit.AuditService;
import com.infusers.core.cache.redis.RedisCacheKeys;
import com.infusers.core.logger.ILogger;
import com.infusers.core.reports.dto.ReportColumn;
import com.infusers.core.reports.dto.ReportData;
import com.infusers.core.reports.dto.ReportRow;
import com.infusers.core.security.common.UserLoggedInEvent;
import com.infusers.core.security.common.UserLoggedOutEvent;
import com.infusers.core.sse.requests.ActiveRequestEntryEvent;
import com.infusers.core.sse.requests.HTTPRequestDto;
import com.infusers.core.user.util.UserUtility;
import com.infusers.core.util.InfusersUtility;

import jakarta.annotation.PostConstruct;

@Service
@EnableScheduling
public class ActiveUserService {

	private final ILogger log = new ILogger(ActiveUserService.class);
	private static final String CLASS_NAME = "ActiveUserService";
	private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss z yyyy");
	
    @Autowired
    private UserUtility userUtility;		   	

	@Autowired
	private Gson gson;    
	
	@Autowired
	private AuditService auditService;

	@Autowired
	private ApplicationEventPublisher eventPublisher;

	@Autowired
	@Qualifier("redisTemplate")
	private RedisTemplate redisTemplate;
	
    @PostConstruct
    public void init() {
        redisTemplate.expire(RedisCacheKeys.ACTIVE_REQUEST_COUNT_KEY, userUtility.getAutoLogoutTimeMinutes(), TimeUnit.MINUTES);    	
    }	

	@EventListener
	public void handleUserLoggedInEvent(UserLoggedInEvent event) {
		try {
			updateUserActivity(event);
    	}
    	catch(Exception e) {
            log.error(CLASS_NAME + ".handleUserLoggedInEvent() -> Error hadling user logged in event: "+ e+" :: User Name = "+event.getUserName()); 
    	}			
	}
	
    @EventListener
    public void handleActiveRequestEntryEvent(ActiveRequestEntryEvent event) {
    	try {
    		HTTPRequestDto requestDto = event.getReqDto();
    		if(requestDto.getUserName()!=null) {
                updateUserRequestActivity(requestDto);    			
    		}
    	}
    	catch(Exception e) {
            log.error(CLASS_NAME + ".handleActiveRequestEntryEvent() -> Error hadling active reqeust event: "+ e+" :: event = "+event.getReqDto()); 
    	}		
    } 
    
    private void updateUserRequestActivity(HTTPRequestDto requestDto) {
    	ActiveUserDto activeUserDto = getDto(null, requestDto);
		redisTemplate.opsForHash().put(RedisCacheKeys.ACTIVE_USERS_COUNT_PREFIX_KEY, activeUserDto.getKey(), gson.toJson(activeUserDto));		
    }

	// Method to update the user's activity timestamp
	private void updateUserActivity(UserLoggedInEvent event) {
    	ActiveUserDto activeUserDto = getDto(event, null);
		redisTemplate.opsForHash().put(RedisCacheKeys.ACTIVE_USERS_COUNT_PREFIX_KEY, activeUserDto.getKey(), gson.toJson(activeUserDto));			
		publishActiveUsersCount();
	}  
	    
    private ActiveUserDto getDto(UserLoggedInEvent event, HTTPRequestDto requestDto) {
    	String userName = null;
    	if(event!=null) {
    		userName = event.getUserName();
    	}
    	else if (requestDto!=null) {
    		userName = requestDto.getUserName();
    	}
    	else {
            log.error(CLASS_NAME + ".getDto() -> Needs attendion!! Both Logged In event & HTTP Request are NULL! event = "+event+" :: requestDto = "+requestDto);    		
    	}
    	
    	String dtoJson = (String) redisTemplate.opsForHash().get(RedisCacheKeys.ACTIVE_USERS_COUNT_PREFIX_KEY, userName);    	
        ActiveUserDto activeUserDto = gson.fromJson(dtoJson, ActiveUserDto.class);        
        
        if (activeUserDto == null) {            
        	if(event!=null) {
                activeUserDto = new ActiveUserDto(event);        		
        	}
        	else if (requestDto!=null) {
                activeUserDto = new ActiveUserDto(requestDto);
        	}
        	else {
                log.error(CLASS_NAME + ".getDto() -> Needs attendion!! activeUserDto is NULL! Also, both Logged In event & HTTP Request are NULL!  event = "+event+" :: requestDto = "+requestDto);    		
        	}
        }
        else if(requestDto!=null){
        	activeUserDto.set(requestDto); 	
        }
        else {
            log.error(CLASS_NAME + ".getDto() -> Needs attendion!! Cached update will fail!! userName = "+userName+" :: event = "+event.toString()+" :: requestDto = "+requestDto+" :: activeUserDto = "+activeUserDto.toString());      	
        }
        return activeUserDto;
    }
    
	@EventListener
	public void handleUserLoggedOutEvent(UserLoggedOutEvent event) {
		logout(event.getUserName());
	}

	private void logout(String userName) {
	    // Deleting a specific user entry from the Redis hash
	    Long deletedCount = redisTemplate.opsForHash().delete(RedisCacheKeys.ACTIVE_USERS_COUNT_PREFIX_KEY, userName);	    
		publishActiveUsersCount();	
		
		if (deletedCount <= 0) {
		    auditService.log(log, ILogger.LogTypes.ERROR, CLASS_NAME, "logout", "", "User exists?? Failed to remove user: " + userName + " :: Active Users# " + getActiveUserCount());
		}		
	}

	// Scheduled task to clean up inactive users
	@Scheduled(fixedRate = 60000) // Runs every 60 seconds
	public void cleanupInactiveUsers() {
		// No need for manual cleanup; Redis will automatically expire entries
		publishActiveUsersCount();
	}

	private void publishActiveUsersCount() {
		long count = getActiveUserCount();
		eventPublisher.publishEvent(new ActiveUserCountEvent(this, count));
	}
	
	// Method to get the number of active users
	public long getActiveUserCount() {
	    // Get the total number of active users
	    long totalActiveUsers = redisTemplate.opsForHash().size(RedisCacheKeys.ACTIVE_USERS_COUNT_PREFIX_KEY);

	    // Check if "anonymousUser" is present in the cache
	    Boolean hasAnonymousUser = redisTemplate.opsForHash()
	            .hasKey(RedisCacheKeys.ACTIVE_USERS_COUNT_PREFIX_KEY, InfusersUtility.ANONYMOUS_USER);

	    // Subtract 1 from the count if "anonymousUser" is present
	    if (hasAnonymousUser != null && hasAnonymousUser) {
	        return totalActiveUsers - 1;
	    }

	    return totalActiveUsers;
	}	
	
    private List getActiveUsersFromCache() {
        Map entries = redisTemplate.opsForHash().entries(RedisCacheKeys.ACTIVE_USERS_COUNT_PREFIX_KEY);
        
        if (entries == null || entries.isEmpty()) {
            // Handle the empty cache scenario if needed
            return Collections.emptyList();
        }

        List activeUsers = new ArrayList<>();
        entries.forEach((key, value) -> {
            // Deserialize JSON string to ActiveUserDto
            String requestDtoJson = (String) value;
            ActiveUserDto userDto = gson.fromJson(requestDtoJson, ActiveUserDto.class);
            activeUsers.add(userDto);
        });
        return activeUsers;
    }  	
	
    private List transformToReportRows(List activeUsers) {
        return activeUsers.stream().map(dto -> {
            // Safely handle null values for each field
            String userName = (dto.getUserName() != null) ? dto.getUserName() : "Unknown"; // Default value if null

            String loggedInTimeStr = (dto.getLoggedInTime() != null) ? 
                dto.getLoggedInTime().toInstant().atZone(ZoneId.of("Asia/Kolkata")).format(FORMATTER) : 
                ""; // Default value if null

            String lastActivityTimeStr = (dto.getLastActivityTime() != null) ? 
                dto.getLastActivityTime().toInstant().atZone(ZoneId.of("Asia/Kolkata")).format(FORMATTER) : 
                ""; // Default value if null

            String requestURI = (dto.getRequestURI() != null) ? dto.getRequestURI() : "N/A"; // Default value if null
            String clientIp = (dto.getClientIp() != null) ? dto.getClientIp() : "N/A"; // Default value if null

            // Avoid passing nulls to List.of()
            return new ReportRow(
                List.of(
                    userName,
                    loggedInTimeStr,
                    lastActivityTimeStr,
                    requestURI,
                    clientIp
                )
            );
        }).collect(Collectors.toList());
    }

	public ReportData generateReport() {
	    List activeUsers = getActiveUsersFromCache();
	    List rows = transformToReportRows(activeUsers);

	    List columns = List.of(
	        new ReportColumn("User Name"),
	        new ReportColumn("Logged In Time"),
	        new ReportColumn("Last Activity Time"),
	        new ReportColumn("Last Request URI"),
	        new ReportColumn("Last Request Client IP")
	    );

	    return new ReportData(
	        "Active Users Report",
	        columns,
	        rows,
	        1,
	        rows.size(),
	        rows.size(),
	        rows.size()
	    );
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy