This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch 4_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/4_0_X by this push:
     new 8f29203b9d [SYNCOPE-1889] improve group search using SCIM extension 
(#1127)
8f29203b9d is described below

commit 8f29203b9d5ad8ecccdbc218c2ab7d2fc1b6a79f
Author: Samuel Garofalo <72073457+samuelg...@users.noreply.github.com>
AuthorDate: Fri Jul 4 13:52:18 2025 +0200

    [SYNCOPE-1889] improve group search using SCIM extension (#1127)
---
 .../syncope/core/persistence/api/dao/GroupDAO.java |  3 +-
 .../common/dao/AbstractAnyMatchDAO.java            |  3 +-
 .../persistence/jpa/dao/repo/GroupRepoExt.java     |  3 +-
 .../persistence/jpa/dao/repo/GroupRepoExtImpl.java | 12 ++++++--
 .../core/persistence/jpa/outer/GroupTest.java      |  3 +-
 .../core/persistence/jpa/outer/UserTest.java       |  6 ++--
 .../persistence/neo4j/dao/repo/GroupRepoExt.java   |  3 +-
 .../neo4j/dao/repo/GroupRepoExtImpl.java           | 13 +++++++--
 .../core/persistence/neo4j/outer/GroupTest.java    |  3 +-
 .../core/persistence/neo4j/outer/UserTest.java     |  6 ++--
 .../java/data/GroupDataBinderImpl.java             |  3 +-
 .../java/pushpull/LDAPMembershipPullActions.java   |  3 +-
 .../pushpull/LDAPMembershipPullActionsTest.java    |  5 +++-
 .../elasticsearch/client/ElasticsearchUtils.java   |  3 +-
 .../flowable/support/SyncopeUserQueryImpl.java     |  3 +-
 .../ext/opensearch/client/OpenSearchUtils.java     |  3 +-
 .../apache/syncope/core/logic/SCIMDataBinder.java  | 32 +++++++++++++---------
 .../syncope/core/logic/SCIMLogicContext.java       |  6 ++--
 .../syncope/core/logic/SCIMDataBinderTest.java     |  4 ++-
 .../org/apache/syncope/fit/core/SCIMITCase.java    | 32 ++++++++++++++++++++++
 20 files changed, 111 insertions(+), 38 deletions(-)

diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java
index bae211073c..075afed7b0 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/GroupDAO.java
@@ -31,6 +31,7 @@ import 
org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.data.domain.Pageable;
 
 public interface GroupDAO extends AnyDAO<Group> {
 
@@ -58,7 +59,7 @@ public interface GroupDAO extends AnyDAO<Group> {
 
     List<AMembership> findAMemberships(Group group);
 
-    List<UMembership> findUMemberships(Group group);
+    List<UMembership> findUMemberships(Group group, Pageable pageable);
 
     List<String> findAMembers(String groupKey);
 
diff --git 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
index b511bc163c..eadd640bc1 100644
--- 
a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
+++ 
b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnyMatchDAO.java
@@ -67,6 +67,7 @@ import 
org.apache.syncope.core.persistence.api.entity.user.User;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
+import org.springframework.data.domain.Pageable;
 import org.springframework.transaction.annotation.Transactional;
 
 public abstract class AbstractAnyMatchDAO implements AnyMatchDAO {
@@ -274,7 +275,7 @@ public abstract class AbstractAnyMatchDAO implements 
AnyMatchDAO {
                         || 
groupDAO.findADynMembers(group).contains(cond.getMember());
             }
         } else {
-            found = groupDAO.findUMemberships(group).stream().
+            found = groupDAO.findUMemberships(group, 
Pageable.unpaged()).stream().
                     anyMatch(memb -> 
memb.getLeftEnd().getKey().equals(cond.getMember()))
                     || 
groupDAO.findUDynMembers(group).contains(cond.getMember());
         }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java
index cc7004b39a..0ff0c94649 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExt.java
@@ -29,6 +29,7 @@ import 
org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.data.domain.Pageable;
 
 public interface GroupRepoExt extends AnyRepoExt<Group> {
 
@@ -48,7 +49,7 @@ public interface GroupRepoExt extends AnyRepoExt<Group> {
 
     List<AMembership> findAMemberships(Group group);
 
-    List<UMembership> findUMemberships(Group group);
+    List<UMembership> findUMemberships(Group group, Pageable pageable);
 
     Group saveAndRefreshDynMemberships(Group group);
 
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java
index e06e96a078..427757ca23 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/GroupRepoExtImpl.java
@@ -69,6 +69,7 @@ import 
org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
 import org.springframework.transaction.annotation.Transactional;
 
 public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group> implements 
GroupRepoExt {
@@ -214,11 +215,16 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group> implements Group
     }
 
     @Override
-    public List<UMembership> findUMemberships(final Group group) {
+    public List<UMembership> findUMemberships(final Group group, final 
Pageable pageable) {
         TypedQuery<UMembership> query = entityManager.createQuery(
-                "SELECT e FROM " + JPAUMembership.class.getSimpleName() + " e 
WHERE e.rightEnd=:group",
+                "SELECT e FROM " + JPAUMembership.class.getSimpleName()
+                        + " e WHERE e.rightEnd=:group ORDER BY e.leftEnd",
                 UMembership.class);
         query.setParameter("group", group);
+        if (pageable.isPaged()) {
+            query.setFirstResult(pageable.getPageSize() * 
pageable.getPageNumber());
+            query.setMaxResults(pageable.getPageSize());
+        }
 
         return query.getResultList();
     }
@@ -308,7 +314,7 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group> implements Group
                     new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, 
leftEnd, AuthContextUtils.getDomain()));
         });
 
-        findUMemberships(group).forEach(membership -> {
+        findUMemberships(group, Pageable.unpaged()).forEach(membership -> {
             User leftEnd = membership.getLeftEnd();
             leftEnd.remove(membership);
             membership.setRightEnd(null);
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
index fbf58712d5..f41d052cbd 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/GroupTest.java
@@ -61,6 +61,7 @@ import 
org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMemb
 import 
org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
 import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -115,7 +116,7 @@ public class GroupTest extends AbstractTest {
     @Test
     public void uMemberships() {
         List<UMembership> memberships = groupDAO.findUMemberships(
-                
groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow());
+                
groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow(), 
Pageable.unpaged());
         assertEquals(2, memberships.size());
         assertTrue(memberships.stream().anyMatch(m -> 
"3d5e91f6-305e-45f9-ad30-4897d3d43bd9".equals(m.getKey())));
         assertTrue(memberships.stream().anyMatch(m -> 
"d53f7657-2b22-4e10-a2cd-c3379a4d1a31".equals(m.getKey())));
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
index cda04cc3ae..b4bb2186df 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/UserTest.java
@@ -51,6 +51,7 @@ import org.apache.syncope.core.persistence.jpa.AbstractTest;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
 import org.springframework.test.annotation.DirtiesContext;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -91,7 +92,7 @@ public class UserTest extends AbstractTest {
     @Test
     public void delete() {
         List<UMembership> memberships = groupDAO.findUMemberships(
-                groupDAO.findByName("managingDirector").orElseThrow());
+                groupDAO.findByName("managingDirector").orElseThrow(), 
Pageable.unpaged());
         assertFalse(memberships.isEmpty());
 
         userDAO.deleteById("c9b2dec2-00a7-4855-97c0-d854842b4b24");
@@ -101,7 +102,8 @@ public class UserTest extends AbstractTest {
         assertTrue(userDAO.findByUsername("bellini").isEmpty());
         assertTrue(plainSchemaDAO.findById("loginDate").isPresent());
 
-        memberships = 
groupDAO.findUMemberships(groupDAO.findByName("managingDirector").orElseThrow());
+        memberships = groupDAO.findUMemberships(
+                groupDAO.findByName("managingDirector").orElseThrow(), 
Pageable.unpaged());
         assertTrue(memberships.isEmpty());
     }
 
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java
index 8486469408..3aace7086e 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExt.java
@@ -29,6 +29,7 @@ import 
org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.data.domain.Pageable;
 
 public interface GroupRepoExt extends AnyRepoExt<Group> {
 
@@ -48,7 +49,7 @@ public interface GroupRepoExt extends AnyRepoExt<Group> {
 
     List<AMembership> findAMemberships(Group group);
 
-    List<UMembership> findUMemberships(Group group);
+    List<UMembership> findUMemberships(Group group, Pageable pageable);
 
     Group saveAndRefreshDynMemberships(Group group);
 
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java
index 4aa022737d..1d537d2816 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/GroupRepoExtImpl.java
@@ -80,6 +80,7 @@ import 
org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.identityconnectors.framework.common.objects.SyncDeltaType;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.neo4j.core.Neo4jClient;
 import org.springframework.data.neo4j.core.Neo4jTemplate;
 import org.springframework.transaction.annotation.Transactional;
@@ -260,11 +261,17 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
     }
 
     @Override
-    public List<UMembership> findUMemberships(final Group group) {
+    public List<UMembership> findUMemberships(final Group group, final 
Pageable pageable) {
+        String paged = "";
+        if (pageable.isPaged()) {
+            paged = " SKIP " + pageable.getPageSize() * 
pageable.getPageNumber()
+                    + " LIMIT " + pageable.getPageSize();
+        }
         return toList(
                 neo4jClient.query(
                         "MATCH (n:" + Neo4jUMembership.NODE + ")-[]-(g:" + 
Neo4jGroup.NODE + " {id: $id}) "
-                        + "RETURN n.id").bindAll(Map.of("id", 
group.getKey())).fetch().all(),
+                                + "RETURN n.id" + paged)
+                        .bindAll(Map.of("id", group.getKey())).fetch().all(),
                 "n.id",
                 Neo4jUMembership.class,
                 null);
@@ -406,7 +413,7 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
                     new EntityLifecycleEvent<>(this, SyncDeltaType.UPDATE, 
leftEnd, AuthContextUtils.getDomain()));
         });
 
-        findUMemberships(group).forEach(membership -> {
+        findUMemberships(group, Pageable.unpaged()).forEach(membership -> {
             User leftEnd = membership.getLeftEnd();
             leftEnd.remove(membership);
             membership.setRightEnd(null);
diff --git 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java
 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java
index 84d0772608..ef74532e6d 100644
--- 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java
+++ 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/GroupTest.java
@@ -57,6 +57,7 @@ import 
org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jADynGroup
 import 
org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUDynGroupMembership;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.neo4j.core.Neo4jTemplate;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -113,7 +114,7 @@ public class GroupTest extends AbstractTest {
     @Test
     public void uMemberships() {
         List<UMembership> memberships = groupDAO.findUMemberships(
-                
groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow());
+                
groupDAO.findById("37d15e4c-cdc1-460b-a591-8505c8133806").orElseThrow(), 
Pageable.unpaged());
         assertEquals(2, memberships.size());
         assertTrue(memberships.stream().anyMatch(m -> 
"3d5e91f6-305e-45f9-ad30-4897d3d43bd9".equals(m.getKey())));
         assertTrue(memberships.stream().anyMatch(m -> 
"d53f7657-2b22-4e10-a2cd-c3379a4d1a31".equals(m.getKey())));
diff --git 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java
 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java
index 3499d17a63..9f596eb402 100644
--- 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java
+++ 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/UserTest.java
@@ -49,6 +49,7 @@ import org.apache.syncope.core.persistence.neo4j.AbstractTest;
 import 
org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jLinkedAccount;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
 import org.springframework.data.neo4j.core.Neo4jTemplate;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -91,7 +92,7 @@ public class UserTest extends AbstractTest {
     @Test
     public void delete() {
         List<UMembership> memberships = groupDAO.findUMemberships(
-                groupDAO.findByName("managingDirector").orElseThrow());
+                groupDAO.findByName("managingDirector").orElseThrow(), 
Pageable.unpaged());
         assertFalse(memberships.isEmpty());
 
         userDAO.deleteById("c9b2dec2-00a7-4855-97c0-d854842b4b24");
@@ -99,7 +100,8 @@ public class UserTest extends AbstractTest {
         assertTrue(userDAO.findByUsername("bellini").isEmpty());
         assertTrue(plainSchemaDAO.findById("loginDate").isPresent());
 
-        memberships = 
groupDAO.findUMemberships(groupDAO.findByName("managingDirector").orElseThrow());
+        memberships = groupDAO.findUMemberships(
+                groupDAO.findByName("managingDirector").orElseThrow(), 
Pageable.unpaged());
         assertTrue(memberships.isEmpty());
     }
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
index e8c11d92ce..9ede39b550 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GroupDataBinderImpl.java
@@ -75,6 +75,7 @@ import 
org.apache.syncope.core.provisioning.api.MappingManager;
 import org.apache.syncope.core.provisioning.api.PropagationByResource;
 import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
 import org.apache.syncope.core.provisioning.java.pushpull.OutboundMatcher;
+import org.springframework.data.domain.Pageable;
 import org.springframework.transaction.annotation.Transactional;
 
 @Transactional(rollbackFor = { Throwable.class })
@@ -580,7 +581,7 @@ public class GroupDataBinderImpl extends AnyDataBinder 
implements GroupDataBinde
 
         Map<String, PropagationByResource<String>> result = new HashMap<>();
 
-        groupDAO.findUMemberships(group).
+        groupDAO.findUMemberships(group, Pageable.unpaged()).
                 forEach((membership) -> populateTransitiveResources(group, 
membership.getLeftEnd(), result));
 
         return result;
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
index 11d4c67d74..51edb543ee 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActions.java
@@ -52,6 +52,7 @@ import 
org.identityconnectors.framework.common.objects.LiveSyncDelta;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -129,7 +130,7 @@ public class LDAPMembershipPullActions implements 
InboundActions {
         }
 
         groupDAO.findUMemberships(groupDAO.findById(entity.getKey()).
-                orElseThrow(() -> new NotFoundException("Group " + 
entity.getKey()))).
+                orElseThrow(() -> new NotFoundException("Group " + 
entity.getKey())), Pageable.unpaged()).
                 forEach(uMembership -> {
                     Set<String> memb = membershipsBefore.computeIfAbsent(
                             uMembership.getLeftEnd().getKey(),
diff --git 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
index a5852e59a0..f7138cc600 100644
--- 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
+++ 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/pushpull/LDAPMembershipPullActionsTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -68,6 +69,7 @@ import org.junit.jupiter.api.Test;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
 import org.springframework.test.util.ReflectionTestUtils;
 
 public class LDAPMembershipPullActionsTest extends AbstractTest {
@@ -142,7 +144,8 @@ public class LDAPMembershipPullActionsTest extends 
AbstractTest {
         ReflectionTestUtils.setField(ldapMembershipPullActions, 
"membershipsAfter", membershipsAfter);
 
         lenient().when(groupDAO.findById(anyString())).thenAnswer(ic -> 
Optional.of(mock(Group.class)));
-        
lenient().when(groupDAO.findUMemberships(any(Group.class))).thenReturn(List.of(uMembership));
+        lenient().when(groupDAO.findUMemberships(any(Group.class), 
eq(Pageable.unpaged())))
+                .thenReturn(List.of(uMembership));
 
         ConnConfPropSchema connConfPropSchema = new ConnConfPropSchema();
         connConfPropSchema.setName("testSchemaName");
diff --git 
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
 
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
index 5a95a11607..1f887cd7c8 100644
--- 
a/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
+++ 
b/ext/elasticsearch/client-elasticsearch/src/main/java/org/apache/syncope/ext/elasticsearch/client/ElasticsearchUtils.java
@@ -44,6 +44,7 @@ import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.data.domain.Pageable;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -146,7 +147,7 @@ public class ElasticsearchUtils {
                 Optional.ofNullable(group.getGroupOwner()).ifPresent(go -> 
builder.put("groupOwner", go.getKey()));
 
                 Set<String> members = new HashSet<>();
-                members.addAll(groupDAO.findUMemberships(group).stream().
+                members.addAll(groupDAO.findUMemberships(group, 
Pageable.unpaged()).stream().
                         map(membership -> 
membership.getLeftEnd().getKey()).toList());
                 members.addAll(groupDAO.findUDynMembers(group));
                 members.addAll(groupDAO.findAMemberships(group).stream().
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java
index 5868a97fc4..ecdf1425e6 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/support/SyncopeUserQueryImpl.java
@@ -27,6 +27,7 @@ import org.flowable.idm.engine.impl.UserQueryImpl;
 import org.flowable.idm.engine.impl.persistence.entity.UserEntity;
 import org.flowable.idm.engine.impl.persistence.entity.UserEntityImpl;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
 import org.springframework.transaction.annotation.Transactional;
 
 public class SyncopeUserQueryImpl extends UserQueryImpl {
@@ -56,7 +57,7 @@ public class SyncopeUserQueryImpl extends UserQueryImpl {
         } else if (groupId != null) {
             groupDAO.findByName(groupId).map(group -> {
                 List<User> r = new ArrayList<>();
-                groupDAO.findUMemberships(group).stream().map(m -> 
fromSyncopeUser(m.getLeftEnd())).
+                groupDAO.findUMemberships(group, 
Pageable.unpaged()).stream().map(m -> fromSyncopeUser(m.getLeftEnd())).
                         filter(user -> !r.contains(user)).
                         forEach(r::add);
                 return r;
diff --git 
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
 
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
index afdb185449..b94ee902dc 100644
--- 
a/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
+++ 
b/ext/opensearch/client-opensearch/src/main/java/org/apache/syncope/ext/opensearch/client/OpenSearchUtils.java
@@ -44,6 +44,7 @@ import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.springframework.data.domain.Pageable;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
@@ -146,7 +147,7 @@ public class OpenSearchUtils {
                 Optional.ofNullable(group.getGroupOwner()).ifPresent(go -> 
builder.put("groupOwner", go.getKey()));
 
                 Set<String> members = new HashSet<>();
-                members.addAll(groupDAO.findUMemberships(group).stream().
+                members.addAll(groupDAO.findUMemberships(group, 
Pageable.unpaged()).stream().
                         map(membership -> 
membership.getLeftEnd().getKey()).toList());
                 members.addAll(groupDAO.findUDynMembers(group));
                 members.addAll(groupDAO.findAMemberships(group).stream().
diff --git 
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
 
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
index e9292daa2b..389e6658d7 100644
--- 
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
+++ 
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
@@ -54,8 +54,11 @@ import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.StatusRType;
 import org.apache.syncope.core.logic.scim.SCIMConfManager;
 import org.apache.syncope.core.persistence.api.dao.AnyDAO;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
 import org.apache.syncope.core.spring.security.AuthDataAccessor;
 import org.apache.syncope.ext.scimv2.api.BadRequestException;
@@ -79,6 +82,7 @@ import org.apache.syncope.ext.scimv2.api.type.Resource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.data.domain.PageRequest;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
 public class SCIMDataBinder {
@@ -123,14 +127,18 @@ public class SCIMDataBinder {
 
     protected final AuthDataAccessor authDataAccessor;
 
+    protected final GroupDAO groupDAO;
+
     public SCIMDataBinder(
             final SCIMConfManager confManager,
             final UserLogic userLogic,
-            final AuthDataAccessor authDataAccessor) {
+            final AuthDataAccessor authDataAccessor,
+            final GroupDAO groupDAO) {
 
         this.confManager = confManager;
         this.userLogic = userLogic;
         this.authDataAccessor = authDataAccessor;
+        this.groupDAO = groupDAO;
     }
 
     protected <E extends Enum<?>> void fill(
@@ -991,6 +999,7 @@ public class SCIMDataBinder {
         return Pair.of(userUR, statusR);
     }
 
+    @Transactional(readOnly = true)
     public SCIMGroup toSCIMGroup(
             final GroupTO groupTO,
             final String location,
@@ -1029,18 +1038,15 @@ public class SCIMDataBinder {
             long count = userLogic.search(
                     searchCond, PageRequest.of(0, 1), 
SyncopeConstants.ROOT_REALM, true, false).getTotalElements();
 
-            for (int page = 0; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE); 
page++) {
-                List<UserTO> users = userLogic.search(
-                        searchCond,
-                        PageRequest.of(page, AnyDAO.DEFAULT_PAGE_SIZE),
-                        SyncopeConstants.ROOT_REALM,
-                        true,
-                        false).
-                        getContent();
-                users.forEach(userTO -> group.getMembers().add(new Member(
-                        userTO.getKey(),
-                        StringUtils.substringBefore(location, "/Groups") + 
"/Users/" + userTO.getKey(),
-                        userTO.getUsername())));
+            for (int page = 0; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; 
page++) {
+                List<UMembership> users = groupDAO.findUMemberships(
+                        groupDAO.findById(groupTO.getKey())
+                                .orElseThrow(() -> new 
NotFoundException("Group " + groupTO.getKey())),
+                        PageRequest.of(page, AnyDAO.DEFAULT_PAGE_SIZE));
+                users.forEach(uMembership -> group.getMembers().add(new Member(
+                        uMembership.getLeftEnd().getKey(),
+                        StringUtils.substringBefore(location, "/Groups") + 
"/Users/" + uMembership.getKey(),
+                        uMembership.getLeftEnd().getUsername())));
             }
         }
 
diff --git 
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java
 
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java
index 559146e273..968dd69dfc 100644
--- 
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java
+++ 
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogicContext.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.core.logic.init.SCIMLoader;
 import org.apache.syncope.core.logic.scim.SCIMConfManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.spring.security.AuthDataAccessor;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
@@ -46,9 +47,10 @@ public class SCIMLogicContext {
     public SCIMDataBinder scimDataBinder(
             final SCIMConfManager confManager,
             final UserLogic userLogic,
-            final AuthDataAccessor authDataAccessor) {
+            final AuthDataAccessor authDataAccessor,
+            final GroupDAO groupDAO) {
 
-        return new SCIMDataBinder(confManager, userLogic, authDataAccessor);
+        return new SCIMDataBinder(confManager, userLogic, authDataAccessor,  
groupDAO);
     }
 
     @ConditionalOnMissingBean
diff --git 
a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
 
b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
index c3f378c147..d65e4b9d63 100644
--- 
a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
+++ 
b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/SCIMDataBinderTest.java
@@ -27,6 +27,7 @@ import java.util.stream.Stream;
 import org.apache.syncope.common.lib.scim.SCIMConf;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.core.logic.scim.SCIMConfManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.spring.security.AuthDataAccessor;
 import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation;
 import org.apache.syncope.ext.scimv2.api.data.SCIMPatchPath;
@@ -50,7 +51,8 @@ class SCIMDataBinderTest {
         when(scimConfManager.get()).thenReturn(new SCIMConf());
         UserLogic userLogic = mock(UserLogic.class);
         AuthDataAccessor authDataAccessor = mock(AuthDataAccessor.class);
-        dataBinder = new SCIMDataBinder(scimConfManager, userLogic, 
authDataAccessor);
+        GroupDAO  groupDAO = mock(GroupDAO.class);
+        dataBinder = new SCIMDataBinder(scimConfManager, userLogic, 
authDataAccessor,  groupDAO);
     }
 
     @ParameterizedTest
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
index 999b2c8dc3..6d8625db70 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
@@ -45,6 +45,7 @@ import java.util.List;
 import java.util.UUID;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.request.GroupUR;
 import org.apache.syncope.common.lib.request.StringPatchItem;
 import org.apache.syncope.common.lib.request.UserUR;
@@ -63,6 +64,7 @@ import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.rest.api.Preference;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.ext.scimv2.api.SCIMConstants;
 import org.apache.syncope.ext.scimv2.api.data.Group;
 import org.apache.syncope.ext.scimv2.api.data.ListResponse;
@@ -385,6 +387,36 @@ public class SCIMITCase extends AbstractITCase {
 
         SCIMGroup additional = groups.getResources().getFirst();
         assertEquals("additional", additional.getDisplayName());
+        List<UserTO> usersList = USER_SERVICE.search(new AnyQuery.Builder()
+                .realm(SyncopeConstants.ROOT_REALM)
+                .page(0)
+                .size(15)
+                .fiql("$groups==additional")
+                .build()).getResult();
+        assertEquals(usersList.size(), additional.getMembers().size());
+
+        // eq to get members
+        response = webClient().path("Groups").query("filter", "displayName eq 
\"child\"").get();
+        assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+        assertEquals(
+                SCIMConstants.APPLICATION_SCIM_JSON,
+                
StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), 
";"));
+
+        groups = response.readEntity(new GenericType<>() {
+        });
+        assertNotNull(groups);
+        assertEquals(1, groups.getTotalResults());
+
+        SCIMGroup child = groups.getResources().getFirst();
+        assertEquals("child", child.getDisplayName());
+        usersList = USER_SERVICE.search(new AnyQuery.Builder()
+                .realm(SyncopeConstants.ROOT_REALM)
+                .page(0)
+                .size(15)
+                .fiql("$groups==child")
+                .build()).getResult();
+        assertEquals(usersList.size(), child.getMembers().size());
+        assertTrue(child.getMembers().stream().anyMatch(member -> 
member.getDisplay().equals("verdi")));
 
         // eq via POST
         SCIMSearchRequest request = new SCIMSearchRequest("displayName eq 
\"additional\"", null, null, null, null);


Reply via email to