Merge pull request #152 from Kitzanos/feature/GEPAFINBE-133

GEPAFINBE-133 (All API responses must be stored in user actions.)
This commit is contained in:
Rinaldo
2025-01-07 09:52:28 +01:00
committed by GitHub
7 changed files with 222 additions and 2 deletions

View File

@@ -245,6 +245,12 @@
<artifactId>reactor-netty</artifactId> <artifactId>reactor-netty</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies> </dependencies>
<repositories> <repositories>
<repository> <repository>

View File

@@ -0,0 +1,20 @@
package net.gepafin.tendermanagement.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}

View File

@@ -355,5 +355,10 @@ public class GepafinConstant {
public static final String NOTIFICATION_DELETED_SUCCESSFULLY="notification.deleted.successfully"; public static final String NOTIFICATION_DELETED_SUCCESSFULLY="notification.deleted.successfully";
public static final String NOTIFICATION_UPDATED_SUCCESSFULLY="notification.updated.successfully"; public static final String NOTIFICATION_UPDATED_SUCCESSFULLY="notification.updated.successfully";
public static final String USER_WITH_COMPANY_NOT_FOUND = "user.with.company.not.found"; public static final String USER_WITH_COMPANY_NOT_FOUND = "user.with.company.not.found";
//action log response
public static final String STATUS_CODE_STRING = "statusCode";
public static final String GET_STATUS_CODE_STRING = "status";
public static final String MESSAGE_STRING = "message";
} }

View File

@@ -6,5 +6,5 @@ import org.springframework.stereotype.Repository;
@Repository @Repository
public interface UserActionsRepository extends JpaRepository<UserActionEntity, Long> { public interface UserActionsRepository extends JpaRepository<UserActionEntity, Long> {
UserActionEntity findUserActionById(Long id); UserActionEntity findUserActionByIdAndIsDeletedFalse(Long id);
} }

View File

@@ -41,6 +41,8 @@ public class LoggingUtil {
@Autowired @Autowired
private TokenProvider tokenProvider; private TokenProvider tokenProvider;
private static final ThreadLocal<Long> userActionIdHolder = new ThreadLocal<>();
public UserActionEntity logUserAction(UserActionRequest userActionRequest) { public UserActionEntity logUserAction(UserActionRequest userActionRequest) {
UserActionEntity userAction = new UserActionEntity(); UserActionEntity userAction = new UserActionEntity();
try { try {
@@ -83,12 +85,22 @@ public class LoggingUtil {
userAction.setResponse(response); userAction.setResponse(response);
userActionsRepository.save(userAction); userActionsRepository.save(userAction);
userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId()); userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId());
userActionIdHolder.set(userAction.getId());
} catch (Exception e) { } catch (Exception e) {
log.error("Error logging user action: {}", e.getMessage(), e); log.error("Error logging user action: {}", e.getMessage(), e);
} }
return userAction; return userAction;
} }
public Long getUserActionId() {
return userActionIdHolder.get();
}
public void clearUserActionId() {
userActionIdHolder.remove();
log.info("UserActionId cleared from ThreadLocal");
}
private String normalizeUrl(String url) { private String normalizeUrl(String url) {
url = url.replaceAll("(?<!:)//+", "/"); url = url.replaceAll("(?<!:)//+", "/");
@@ -263,6 +275,7 @@ public class LoggingUtil {
userAction.setResponse(response); userAction.setResponse(response);
userActionsRepository.save(userAction); userActionsRepository.save(userAction);
userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId()); userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId());
} catch (Exception e) { } catch (Exception e) {
log.error("Error logging user action: {}", e.getMessage(), e); log.error("Error logging user action: {}", e.getMessage(), e);
} }
@@ -323,10 +336,35 @@ public class LoggingUtil {
public UserActionEntity getUserActionLogById(Long id) { public UserActionEntity getUserActionLogById(Long id) {
return userActionsRepository.findUserActionById(id); return userActionsRepository.findUserActionByIdAndIsDeletedFalse(id);
} }
public List<VersionHistoryEntity> getVersionHistoryLogById(Long id) { public List<VersionHistoryEntity> getVersionHistoryLogById(Long id) {
return versionHistoryRepository.findVersionHistoryByUserActionId(id); return versionHistoryRepository.findVersionHistoryByUserActionId(id);
} }
public void updateUserActionWithError(Long userActionId, String errorDetails) {
if (userActionId == null) {
return;
}
UserActionEntity userAction = userActionsRepository.findUserActionByIdAndIsDeletedFalse(userActionId);
if (userAction != null) {
userAction.setResponse(errorDetails);
userActionsRepository.save(userAction);
}
}
public void updateUserActionWithResponse(Long userActionId, String response) {
if (userActionId == null) {
return;
}
UserActionEntity userAction = userActionsRepository.findUserActionByIdAndIsDeletedFalse(userActionId);
if (userAction != null) {
userAction.setResponse(response);
userActionsRepository.save(userAction);
}
}
} }

View File

@@ -0,0 +1,148 @@
package net.gepafin.tendermanagement.util;
import com.amazonaws.services.alexaforbusiness.model.UnauthorizedException;
import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException;
import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException;
import net.gepafin.tendermanagement.web.rest.api.errors.Status;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.server.ResponseStatusException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.AccessDeniedException;
import java.util.LinkedHashMap;
import java.util.Map;
@Aspect
@Component
@Slf4j
public class UserActionAspect {
@Autowired
private LoggingUtil loggingUtil;
@Around("execution(public * net.gepafin.tendermanagement.web.rest.api.impl..*(..))")
public Object logApiResponse(ProceedingJoinPoint joinPoint) throws Throwable {
Object result;
HttpServletRequest request = getRequestFromContext();
try {
Long userActionId = getUserActionIdFromRequest(request);
if (userActionId != null) {
request.setAttribute(GepafinConstant.USER_ACTION_ID, userActionId);
log.info("Stored userActionId in RequestContext: {}", userActionId);
} else {
userActionId = loggingUtil.getUserActionId();
if (userActionId != null) {
request.setAttribute(GepafinConstant.USER_ACTION_ID, userActionId);
}
}
result = joinPoint.proceed();
if (result instanceof ResponseEntity<?>) {
Long storedUserActionId = (Long) request.getAttribute(GepafinConstant.USER_ACTION_ID);
handleSuccessResponse((ResponseEntity<?>) result, storedUserActionId == null ? userActionId : storedUserActionId);
}
} catch (Exception ex) {
log.error("Exception occurred: ", ex);
handleError(ex, getUserActionIdFromRequest(request));
throw ex;
} finally {
loggingUtil.clearUserActionId();
}
return result;
}
private void handleSuccessResponse(ResponseEntity<?> responseEntity, Long userActionId) {
if (userActionId != null) {
Map<String, Object> responseWithUserAction = new LinkedHashMap<>();
responseWithUserAction.put(GepafinConstant.STATUS_CODE_STRING, responseEntity.getStatusCode().value());
// Log and update user action
loggingUtil.updateUserActionWithResponse(userActionId, Utils.convertMapIntoJsonString(responseWithUserAction));
log.info("Updated userActionId with response: {}", userActionId);
}
}
private void handleError(Throwable ex, Long userActionId) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
HttpStatus status = getStatusCodeFromException(ex);
log.info("Status Code received from exception : {}", status);
String errorMessage = ex.getMessage();
Map<String, Object> errorResponse = new LinkedHashMap<>();
errorResponse.put(GepafinConstant.STATUS_CODE_STRING, status.value());
errorResponse.put(GepafinConstant.GET_STATUS_CODE_STRING, status);
errorResponse.put(GepafinConstant.MESSAGE_STRING, errorMessage);
if (userActionId != null) {
String errorDetails = Utils.convertMapIntoJsonString(errorResponse);
loggingUtil.updateUserActionWithError(userActionId, errorDetails);
log.info("Updated userActionId with error details: {}", userActionId);
}
}
private HttpServletRequest getRequestFromContext() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes != null ? attributes.getRequest() : null;
}
private Long getUserActionIdFromRequest(HttpServletRequest request) {
if (request != null) {
Object userActionIdAttr = request.getAttribute(GepafinConstant.USER_ACTION_ID);
return userActionIdAttr != null ? Long.valueOf(userActionIdAttr.toString()) : null;
}
return null;
}
private HttpStatus getStatusCodeFromException(Throwable ex) {
if (ex instanceof ResourceNotFoundException) {
return HttpStatus.NOT_FOUND;
}
if (ex instanceof ResponseStatusException responseStatusException) {
return (HttpStatus) responseStatusException.getStatusCode();
}
if (ex instanceof CustomValidationException) {
return HttpStatus.BAD_REQUEST;
}
if (ex instanceof EntityNotFoundException) {
return HttpStatus.NOT_FOUND;
}
if (ex instanceof IllegalArgumentException || ex instanceof MissingServletRequestParameterException || ex instanceof MethodArgumentNotValidException) {
return HttpStatus.BAD_REQUEST;
}
if (ex instanceof AccessDeniedException) {
return HttpStatus.FORBIDDEN;
}
if (ex instanceof UnauthorizedException) {
return HttpStatus.UNAUTHORIZED;
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}

View File

@@ -154,6 +154,9 @@ public class Utils {
public static String convertMapIntoJsonString(Map<String, Object> map) { public static String convertMapIntoJsonString(Map<String, Object> map) {
try { try {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
if (MapUtils.isNotEmpty(map)) { if (MapUtils.isNotEmpty(map)) {
return mapper.writeValueAsString(map); return mapper.writeValueAsString(map);
} }