From e4fa8ae705d9036c7b122ba7b5f8e8fb2a437ec9 Mon Sep 17 00:00:00 2001
From: jcorreale <jean.correale@consultant.aruba.it>
Date: Thu, 13 Feb 2025 16:30:07 +0100
Subject: [PATCH] In-memory sorting of roles returned by RoleService#search

Changelog: added
---
 .../services/impl/RoleServiceImpl.java        | 56 +++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/src/main/java/eu/europa/ec/simpl/usersroles/services/impl/RoleServiceImpl.java b/src/main/java/eu/europa/ec/simpl/usersroles/services/impl/RoleServiceImpl.java
index 05c95fd7..41b7fbc9 100644
--- a/src/main/java/eu/europa/ec/simpl/usersroles/services/impl/RoleServiceImpl.java
+++ b/src/main/java/eu/europa/ec/simpl/usersroles/services/impl/RoleServiceImpl.java
@@ -14,17 +14,25 @@ import eu.europa.ec.simpl.usersroles.services.KeycloakUserService;
 import eu.europa.ec.simpl.usersroles.services.RoleService;
 import jakarta.ws.rs.ClientErrorException;
 import jakarta.ws.rs.NotFoundException;
+import java.beans.PropertyDescriptor;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.UUID;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import lombok.SneakyThrows;
 import lombok.extern.log4j.Log4j2;
 import org.apache.commons.lang3.StringUtils;
 import org.keycloak.representations.idm.RoleRepresentation;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeansException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
 import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -203,6 +211,7 @@ public class RoleServiceImpl implements RoleService {
         var filteredRoles = roleList.stream()
                 .map(role -> roleMapper.toDto(role, getEnabledAttrs(role)))
                 .filter(roleDTO -> shouldMatchFilter(roleDTO, request))
+                .sorted(toComparator(pageable.getSort()))
                 .skip(pageable.getOffset())
                 .limit(pageable.getPageSize())
                 .toList();
@@ -215,6 +224,53 @@ public class RoleServiceImpl implements RoleService {
         return new PageImpl<>(filteredRoles, pageable, totalElements);
     }
 
+    private Comparator<? super RoleDTO> toComparator(Sort sort) {
+        return sort.stream()
+                .flatMap(this::toComparator)
+                .reduce(Comparator::thenComparing)
+                .orElse(unsortedComparator());
+    }
+
+    private Stream<Comparator<RoleDTO>> toComparator(Sort.Order order) {
+        try {
+            return Optional.ofNullable(getPropertyDescriptor(order))
+                    .flatMap(property -> toComparator(property, order.getDirection()))
+                    .stream();
+        } catch (BeansException e) {
+            return Stream.empty();
+        }
+    }
+
+    private static PropertyDescriptor getPropertyDescriptor(Sort.Order order) {
+        return BeanUtils.getPropertyDescriptor(RoleDTO.class, order.getProperty());
+    }
+
+    private static Optional<Comparator<RoleDTO>> toComparator(PropertyDescriptor property, Sort.Direction direction) {
+        if (Comparable.class.isAssignableFrom(property.getPropertyType())) {
+            return Optional.of(buildComparator(property, direction));
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    private static Comparator<RoleDTO> buildComparator(PropertyDescriptor property, Sort.Direction direction) {
+        var comparator = Comparator.<RoleDTO, Comparable<Object>>comparing(o -> get(o, property));
+        if (direction == Sort.Direction.DESC) {
+            comparator = comparator.reversed();
+        }
+        return comparator;
+    }
+
+    private static Comparator<RoleDTO> unsortedComparator() {
+        return (o1, o2) -> 0;
+    }
+
+    @SneakyThrows
+    @SuppressWarnings("unchecked")
+    private static Comparable<Object> get(RoleDTO o, PropertyDescriptor property) {
+        return (Comparable<Object>) property.getReadMethod().invoke(o);
+    }
+
     @Override
     public void importRoles(List<KeycloakRoleDTO> roles) {
         keycloakService.importRoles(roles);
-- 
GitLab