diff --git a/build.gradle b/build.gradle index d258db5..bcdd19a 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'com.flexcodelabs' -version = '0.0.34' +version = '0.0.35' description = 'Flextuma App' java { diff --git a/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java b/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java index 8637d8d..d6f4b7f 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/config/RequestLoggingFilter.java @@ -26,7 +26,8 @@ public class RequestLoggingFilter extends OncePerRequestFilter { private static final Logger log = LoggerFactory.getLogger("FLEXTUMA"); - private static final String USERNAME_KEY = "username"; + private static final String USERNAME = "username"; + private static final String SYSTEM = "SYSTEM"; @Override protected boolean shouldNotFilterErrorDispatch() { @@ -84,7 +85,7 @@ private void logRequest(HttpServletRequest request, HttpServletResponse response String coloredMethod = logColor + request.getMethod() + reset; String coloredUri = logColor + fullUri + reset; - org.slf4j.MDC.put(USERNAME_KEY, username); + org.slf4j.MDC.put(USERNAME, username); try { if (isError) { log.error("{} {} {} {} {}ms - Status: {}", statusLog, userInfo, coloredMethod, coloredUri, duration, @@ -94,25 +95,73 @@ private void logRequest(HttpServletRequest request, HttpServletResponse response status); } } finally { - org.slf4j.MDC.remove(USERNAME_KEY); + org.slf4j.MDC.remove(USERNAME); } } private String getUsername(HttpServletRequest request) { + String username = getCapturedUsername(request); + if (username != null) { + return username; + } + + username = getPrincipalUsername(request); + if (username != null) { + return username; + } + + username = getAuthenticationUsername(); + if (username != null) { + return username; + } + + username = getSessionUsername(request); + if (username != null) { + return username; + } + + username = getLoginUsername(request); + if (username != null) { + return username; + } + + log.debug("Returning SYSTEM as fallback"); + return SYSTEM; + } + + private String getCapturedUsername(HttpServletRequest request) { Object capturedUsername = request.getAttribute(AuthenticatedUserCaptureFilter.REQUEST_USERNAME_ATTRIBUTE); if (capturedUsername instanceof String username && !username.trim().isEmpty() - && !"SYSTEM".equalsIgnoreCase(username)) { + && !SYSTEM.equalsIgnoreCase(username)) { return username; } + return null; + } + private String getPrincipalUsername(HttpServletRequest request) { Principal principal = request.getUserPrincipal(); if (principal != null && principal.getName() != null && !principal.getName().trim().isEmpty()) { return principal.getName(); } + return null; + } + private String getAuthenticationUsername() { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + logAuthenticationDetails(auth); + if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getPrincipal())) { + String username = auth.getName(); + if (username != null && !username.trim().isEmpty() && !SYSTEM.equalsIgnoreCase(username)) { + log.debug("Returning username: {}", username); + return username; + } + } + return null; + } + + private void logAuthenticationDetails(Authentication auth) { log.debug("Authentication found: {}", auth != null); if (auth != null) { log.debug("Auth class: {}", auth.getClass().getSimpleName()); @@ -124,15 +173,9 @@ private String getUsername(HttpServletRequest request) { } else { log.debug("Authentication is null"); } + } - if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getPrincipal())) { - String username = auth.getName(); - if (username != null && !username.trim().isEmpty() && !"SYSTEM".equalsIgnoreCase(username)) { - log.debug("Returning username: {}", username); - return username; - } - } - + private String getSessionUsername(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { Object contextAttr = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); @@ -142,21 +185,22 @@ private String getUsername(HttpServletRequest request) { && !"anonymousUser".equals(sessionAuth.getPrincipal())) { String sessionUsername = sessionAuth.getName(); if (sessionUsername != null && !sessionUsername.trim().isEmpty() - && !"SYSTEM".equalsIgnoreCase(sessionUsername)) { + && !SYSTEM.equalsIgnoreCase(sessionUsername)) { return sessionUsername; } } } } + return null; + } + private String getLoginUsername(HttpServletRequest request) { if (request != null && request.getRequestURI().contains("/login")) { - String loginUsername = request.getParameter(USERNAME_KEY); + String loginUsername = request.getParameter(USERNAME); if (loginUsername != null && !loginUsername.trim().isEmpty()) { return loginUsername; } } - - log.debug("Returning SYSTEM as fallback"); - return "SYSTEM"; + return null; } } diff --git a/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java b/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java index 8777d07..0cfc559 100644 --- a/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java +++ b/src/main/java/com/flexcodelabs/flextuma/core/services/BaseService.java @@ -16,11 +16,7 @@ import jakarta.persistence.criteria.*; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.EntityType; -import jakarta.persistence.metamodel.ManagedType; -import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -54,12 +50,18 @@ public void setEventPublisher(ApplicationEventPublisher eventPublisher) { } private CurrentUserResolver currentUserResolver; + private EntityResponseInitializer entityResponseInitializer; @org.springframework.beans.factory.annotation.Autowired public void setCurrentUserResolver(CurrentUserResolver currentUserResolver) { this.currentUserResolver = currentUserResolver; } + @Autowired + public void setEntityResponseInitializer(EntityResponseInitializer entityResponseInitializer) { + this.entityResponseInitializer = entityResponseInitializer; + } + protected abstract JpaRepository getRepository(); protected abstract String getReadPermission(); @@ -182,7 +184,7 @@ private Specification buildTenantSpec() { } private Pagination buildPaginatedResponse(Page resultPage, Pageable pageable) { - resultPage.getContent().forEach(entity -> initializeAssociationsForResponse(entity, 1)); + resultPage.getContent().forEach(this::initializeAssociationsForResponse); return Pagination.builder() .page(pageable.getPageNumber() + 1) .total(resultPage.getTotalElements()) @@ -196,7 +198,7 @@ public List findAll() { checkPermission(getReadPermission()); Specification spec = buildTenantSpec(); List results = getRepositoryAsExecutor().findAll(spec); - results.forEach(entity -> initializeAssociationsForResponse(entity, 1)); + results.forEach(this::initializeAssociationsForResponse); return results; } @@ -223,7 +225,7 @@ private List doFindAll(String fields, List filter, String rootJoin) { spec = spec.and(buildFetchSpec(fields)); } List results = getRepositoryAsExecutor().findAll(spec); - results.forEach(entity -> initializeAssociationsForResponse(entity, 1)); + results.forEach(this::initializeAssociationsForResponse); return results; } @@ -231,7 +233,7 @@ private List doFindAll(String fields, List filter, String rootJoin) { public Optional findById(UUID id) { checkPermission(getReadPermission()); Optional result = getRepository().findById(id); - result.ifPresent(entity -> initializeAssociationsForResponse(entity, 1)); + result.ifPresent(this::initializeAssociationsForResponse); return result; } @@ -244,7 +246,7 @@ public Optional findById(UUID id, String fields) { spec = spec.and(buildFetchSpec(fields)); } Optional result = getRepositoryAsExecutor().findOne(spec); - result.ifPresent(entity -> initializeAssociationsForResponse(entity, 1)); + result.ifPresent(this::initializeAssociationsForResponse); return result; } @@ -253,7 +255,7 @@ public T save(T entity) { checkPermission(getAddPermission()); onPreSave(entity); T saved = getRepository().save(entity); - initializeAssociationsForResponse(saved, 1); + initializeAssociationsForResponse(saved); onPostSave(saved); eventPublisher.publishEvent(new EntityEvent<>(this, saved, EntityEvent.EntityEventType.CREATED)); return saved; @@ -269,7 +271,7 @@ public T update(UUID id, T entity) { String[] excludedFields = getNullPropertyNames(entity); org.springframework.beans.BeanUtils.copyProperties(entity, existing, excludedFields); T saved = getRepository().save(existing); - initializeAssociationsForResponse(saved, 1); + initializeAssociationsForResponse(saved); eventPublisher.publishEvent(new EntityEvent<>(this, saved, EntityEvent.EntityEventType.UPDATED)); return saved; } @@ -445,97 +447,9 @@ protected void onPreSave(T entity) { protected void onPostSave(T entity) { } - protected void initializeAssociationsForResponse(Object entity, int depth) { - if (!shouldProcessEntity(entity, depth)) { - return; - } - - Hibernate.initialize(entity); - ManagedType managedType = resolveManagedType(Hibernate.getClass(entity)); - if (managedType == null) { - return; - } - - processAssociations(entity, managedType, depth); - } - - private boolean shouldProcessEntity(Object entity, int depth) { - return entity != null && depth >= 0; - } - - private void processAssociations(Object entity, ManagedType managedType, int depth) { - BeanWrapper wrapper = new BeanWrapperImpl(entity); - for (Attribute attribute : managedType.getAttributes()) { - if (!isProcessableAssociation(attribute, wrapper)) { - continue; - } - - Object value = wrapper.getPropertyValue(attribute.getName()); - if (value != null) { - Hibernate.initialize(value); - processAssociationValue(value, depth); - } - } - } - - private boolean isProcessableAssociation(Attribute attribute, BeanWrapper wrapper) { - return attribute.isAssociation() && wrapper.isReadableProperty(attribute.getName()); - } - - private void processAssociationValue(Object value, int depth) { - if (depth == 0) { - return; - } - - if (value instanceof Collection collection) { - processCollectionAssociations(collection, depth); - } else { - initializeSingularAssociations(value, depth - 1); - } - } - - private void processCollectionAssociations(Collection collection, int depth) { - for (Object item : collection) { - initializeSingularAssociations(item, depth - 1); - } - } - - private void initializeSingularAssociations(Object entity, int depth) { - if (entity == null || depth < 0) { - return; - } - - Hibernate.initialize(entity); - ManagedType managedType = resolveManagedType(Hibernate.getClass(entity)); - if (managedType == null) { - return; - } - - BeanWrapper wrapper = new BeanWrapperImpl(entity); - for (Attribute attribute : managedType.getAttributes()) { - if (!attribute.isAssociation() || attribute.isCollection() - || !wrapper.isReadableProperty(attribute.getName())) { - continue; - } - - Object value = wrapper.getPropertyValue(attribute.getName()); - if (value != null) { - Hibernate.initialize(value); - if (depth > 0) { - initializeSingularAssociations(value, depth - 1); - } - } - } - } - - private ManagedType resolveManagedType(Class javaType) { - try { - if (entityManager == null || entityManager.getMetamodel() == null) { - return null; - } - return entityManager.getMetamodel().managedType(javaType); - } catch (IllegalArgumentException ex) { - return null; + protected void initializeAssociationsForResponse(Object entity) { + if (entityResponseInitializer != null) { + entityResponseInitializer.initialize(entity); } } diff --git a/src/main/java/com/flexcodelabs/flextuma/core/services/EntityResponseInitializer.java b/src/main/java/com/flexcodelabs/flextuma/core/services/EntityResponseInitializer.java new file mode 100644 index 0000000..1021aa7 --- /dev/null +++ b/src/main/java/com/flexcodelabs/flextuma/core/services/EntityResponseInitializer.java @@ -0,0 +1,102 @@ +package com.flexcodelabs.flextuma.core.services; + +import java.util.Collection; + +import org.hibernate.Hibernate; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; +import org.springframework.stereotype.Component; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.metamodel.Attribute; +import jakarta.persistence.metamodel.ManagedType; + +@Component +public class EntityResponseInitializer { + + @PersistenceContext + private EntityManager entityManager; + + public void initialize(Object entity) { + initialize(entity, 1); + } + + public void initialize(Object entity, int depth) { + if (entity == null || depth < 0) { + return; + } + + Hibernate.initialize(entity); + ManagedType managedType = resolveManagedType(Hibernate.getClass(entity)); + if (managedType == null) { + return; + } + + BeanWrapper wrapper = new BeanWrapperImpl(entity); + for (Attribute attribute : managedType.getAttributes()) { + if (!attribute.isAssociation() || !wrapper.isReadableProperty(attribute.getName())) { + continue; + } + + Object value = wrapper.getPropertyValue(attribute.getName()); + if (value == null) { + continue; + } + + Hibernate.initialize(value); + if (depth == 0) { + continue; + } + + if (value instanceof Collection collection) { + for (Object item : collection) { + initializeSingularAssociations(item, depth - 1); + } + continue; + } + + initializeSingularAssociations(value, depth - 1); + } + } + + private void initializeSingularAssociations(Object entity, int depth) { + if (entity == null || depth < 0) { + return; + } + + Hibernate.initialize(entity); + ManagedType managedType = resolveManagedType(Hibernate.getClass(entity)); + if (managedType == null) { + return; + } + + BeanWrapper wrapper = new BeanWrapperImpl(entity); + for (Attribute attribute : managedType.getAttributes()) { + if (!attribute.isAssociation() || attribute.isCollection() || !wrapper.isReadableProperty(attribute.getName())) { + continue; + } + + Object value = wrapper.getPropertyValue(attribute.getName()); + if (value == null) { + continue; + } + + Hibernate.initialize(value); + if (depth > 0) { + initializeSingularAssociations(value, depth - 1); + } + } + } + + private ManagedType resolveManagedType(Class javaType) { + try { + if (entityManager == null || entityManager.getMetamodel() == null) { + return null; + } + return entityManager.getMetamodel().managedType(javaType); + } catch (IllegalArgumentException ex) { + return null; + } + } +} diff --git a/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/NotificationService.java b/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/NotificationService.java index 503a5ea..78bd8ed 100644 --- a/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/NotificationService.java +++ b/src/main/java/com/flexcodelabs/flextuma/modules/notification/services/NotificationService.java @@ -3,7 +3,6 @@ import java.util.Map; import java.util.Optional; -import org.hibernate.Hibernate; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,6 +20,7 @@ import com.flexcodelabs.flextuma.core.repositories.SmsLogRepository; import com.flexcodelabs.flextuma.core.repositories.SmsTemplateRepository; import com.flexcodelabs.flextuma.core.repositories.UserRepository; +import com.flexcodelabs.flextuma.core.services.EntityResponseInitializer; import com.flexcodelabs.flextuma.modules.finance.services.WalletService; import com.flexcodelabs.flextuma.core.services.RateLimiterService; import java.util.UUID; @@ -41,6 +41,7 @@ public class NotificationService { private final WalletService walletService; private final RateLimiterService rateLimiterService; private final SmsSegmentCalculator segmentCalculator; + private final EntityResponseInitializer entityResponseInitializer; @Value("${flextuma.sms.price-per-segment:1.0}") private BigDecimal pricePerSegment; @@ -148,27 +149,7 @@ private SmsLog processAndSaveSms(User user, SmsConnector connector, String phone } SmsLog savedLog = logRepository.save(log); - initializeForResponse(savedLog); + entityResponseInitializer.initialize(savedLog); return savedLog; } - - private void initializeForResponse(SmsLog smsLog) { - Hibernate.initialize(smsLog); - - if (smsLog.getCreatedBy() != null) { - Hibernate.initialize(smsLog.getCreatedBy()); - } - - if (smsLog.getUpdatedBy() != null) { - Hibernate.initialize(smsLog.getUpdatedBy()); - } - - if (smsLog.getConnector() != null) { - Hibernate.initialize(smsLog.getConnector()); - } - - if (smsLog.getTemplate() != null) { - Hibernate.initialize(smsLog.getTemplate()); - } - } }