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

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


The following commit(s) were added to refs/heads/master by this push:
     new 30f872f9a4 [SYNCOPE-1830] Supporting membership attributes query with 
Neo4j (#870)
30f872f9a4 is described below

commit 30f872f9a4bd8b5d2b655a26eb7900d8c1deb54d
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Wed Oct 23 12:51:21 2024 +0200

    [SYNCOPE-1830] Supporting membership attributes query with Neo4j (#870)
---
 .../client/console/panels/AnyDirectoryPanel.java   |   4 +-
 .../org/apache/syncope/core/logic/UserLogic.java   |   2 +-
 .../jpa/dao/repo/AbstractAnyRepoExt.java           |   2 +-
 .../jpa/dao/repo/AnyObjectRepoExtImpl.java         |   2 +-
 .../persistence/jpa/dao/repo/UserRepoExtImpl.java  |   4 +-
 .../core/persistence/jpa/outer/AnySearchTest.java  |  39 ++++++
 .../core/persistence/jpa/outer/GroupTest.java      |   4 +-
 .../core/persistence/neo4j/dao/AbstractDAO.java    |   2 +-
 .../persistence/neo4j/dao/Neo4jAnySearchDAO.java   | 118 +++++++++++++++--
 .../persistence/neo4j/dao/Neo4jAuditEventDAO.java  |   2 +-
 .../neo4j/dao/repo/AbstractAnyRepoExt.java         |  12 +-
 .../persistence/neo4j/dao/repo/AnyRepoExt.java     |  13 ++
 .../neo4j/dao/repo/AnyTypeClassRepoExtImpl.java    |   5 +-
 .../neo4j/dao/repo/GroupRepoExtImpl.java           |   2 +-
 .../neo4j/entity/AbstractGroupableRelatable.java   |   2 +-
 .../persistence/neo4j/inner/AnySearchTest.java     |   2 +
 .../persistence/neo4j/outer/AnySearchTest.java     |  57 ++++++++
 .../src/test/resources/domains/MasterContent.xml   |   7 +-
 .../provisioning/java/DefaultMappingManager.java   |   3 +-
 .../java/data/AbstractAnyDataBinder.java           |   4 +-
 .../java/data/ResourceDataBinderImpl.java          |   2 +-
 .../propagation/DefaultPropagationManager.java     |   2 +-
 .../LDAPMembershipPropagationActions.java          |   2 +-
 .../provisioning/java/pushpull/InboundMatcher.java |   3 +-
 .../java/pushpull/SinglePullJobDelegate.java       |   2 +-
 .../java/pushpull/SinglePushJobDelegate.java       |   2 +-
 .../pushpull/stream/StreamPullJobDelegate.java     |   2 +-
 .../pushpull/stream/StreamPushJobDelegate.java     |   2 +-
 .../provisioning/java/utils/ConnObjectUtils.java   |   2 +-
 .../java/AbstractAnyObjectWorkflowAdapter.java     |   2 +-
 .../workflow/java/AbstractUserWorkflowAdapter.java |   2 +-
 .../elasticsearch/client/ElasticsearchUtils.java   |  14 +-
 .../dao/ElasticsearchRealmSearchDAO.java           |   6 +-
 .../syncope/core/logic/oidc/OIDCUserManager.java   |   3 +-
 .../ext/opensearch/client/OpenSearchUtils.java     |  14 +-
 .../opensearch/dao/OpenSearchRealmSearchDAO.java   |   6 +-
 .../core/logic/saml2/SAML2SP4UIUserManager.java    |   3 +-
 .../org/apache/syncope/fit/core/SearchITCase.java  | 145 +++++++++++++--------
 pom.xml                                            |   4 +-
 .../wa/starter/mapping/DefaultAuthMapper.java      |   3 +-
 40 files changed, 367 insertions(+), 140 deletions(-)

diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
index 50f5d63890..6026563e9b 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyDirectoryPanel.java
@@ -188,13 +188,13 @@ public abstract class AnyDirectoryPanel<A extends AnyTO, 
E extends AbstractAnyRe
 
         
PreferenceManager.getList(DisplayAttributesModalPanel.getPrefPlainAttributeView(type)).stream().
                 map(a -> plainSchemas.stream().filter(p -> 
p.getKey().equals(a)).findFirst()).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 forEach(s -> prefcolumns.add(new AttrColumn<>(
                 s.getKey(), 
s.getLabel(SyncopeConsoleSession.get().getLocale()), SchemaType.PLAIN)));
 
         
PreferenceManager.getList(DisplayAttributesModalPanel.getPrefDerivedAttributeView(type)).stream().
                 map(a -> derSchemas.stream().filter(p -> 
p.getKey().equals(a)).findFirst()).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 forEach(s -> prefcolumns.add(new AttrColumn<>(
                 s.getKey(), 
s.getLabel(SyncopeConsoleSession.get().getLocale()), SchemaType.DERIVED)));
 
diff --git 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
index fe4b06cb73..44d2aedf3f 100644
--- 
a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
+++ 
b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/UserLogic.java
@@ -398,7 +398,7 @@ public class UserLogic extends AbstractAnyLogic<UserTO, 
UserCR, UserUR> {
                     orElseThrow(() -> new NotFoundException("Realm " + 
query.getRealm()));
         }
         Set<ExternalResource> resources = query.getResources().stream().
-                
map(resourceDAO::findById).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toSet());
+                
map(resourceDAO::findById).flatMap(Optional::stream).collect(Collectors.toSet());
         if (realm == null && resources.isEmpty()) {
             sce.getElements().add("Nothing to check");
             throw sce;
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java
index b84e8cb7b2..2786163b67 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AbstractAnyRepoExt.java
@@ -228,7 +228,7 @@ public abstract class AbstractAnyRepoExt<A extends Any<?>> 
implements AnyRepoExt
         List<Object> result = query.getResultList();
         return result.stream().
                 map(dynRealm -> dynRealmDAO.findById(dynRealm.toString())).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 map(DynRealm::getKey).
                 distinct().
                 toList();
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
index fb919cd819..6bff4c4e72 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyObjectRepoExtImpl.java
@@ -232,7 +232,7 @@ public class AnyObjectRepoExtImpl extends 
AbstractAnyRepoExt<AnyObject> implemen
         List<Object> result = query.getResultList();
         return result.stream().
                 map(groupKey -> groupDAO.findById(groupKey.toString())).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 distinct().
                 collect(Collectors.toList());
     }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
index 3903f3551d..20d04067b3 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/UserRepoExtImpl.java
@@ -234,7 +234,7 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User> implements UserRep
         List<Object> result = query.getResultList();
         return result.stream().
                 map(roleKey -> roleDAO.findById(roleKey.toString())).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 distinct().
                 collect(Collectors.toList());
     }
@@ -250,7 +250,7 @@ public class UserRepoExtImpl extends 
AbstractAnyRepoExt<User> implements UserRep
         List<Object> result = query.getResultList();
         return result.stream().
                 map(groupKey -> groupDAO.findById(groupKey.toString())).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 distinct().
                 collect(Collectors.toList());
     }
diff --git 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
index 36cbc8e30f..0f0c6ca389 100644
--- 
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
+++ 
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/AnySearchTest.java
@@ -32,6 +32,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import 
org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -44,6 +45,9 @@ import 
org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
+import org.apache.syncope.core.persistence.api.entity.anyobject.APlainAttr;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
@@ -66,6 +70,9 @@ public class AnySearchTest extends AbstractTest {
     @Autowired
     private GroupDAO groupDAO;
 
+    @Autowired
+    private AnyObjectDAO anyObjectDAO;
+
     @Autowired
     private AnySearchDAO searchDAO;
 
@@ -148,6 +155,38 @@ public class AnySearchTest extends AbstractTest {
         assertEquals(rossini.getKey(), users.get(0).getKey());
     }
 
+    @Test
+    public void searchByMembershipAttribute() {
+        AttrCond attrCond = new AttrCond(AttrCond.Type.EQ);
+        attrCond.setSchema("ctype");
+        attrCond.setExpression("otherchildctype");
+        SearchCond cond = SearchCond.getLeaf(attrCond);
+
+        List<AnyObject> results = searchDAO.search(cond, 
AnyTypeKind.ANY_OBJECT);
+        assertTrue(results.isEmpty());
+
+        // add any object membership and its plain attribute
+        AnyObject anyObject = 
anyObjectDAO.findById("8559d14d-58c2-46eb-a2d4-a7d35161e8f8").orElseThrow();
+        AMembership memb = entityFactory.newEntity(AMembership.class);
+        memb.setLeftEnd(anyObject);
+        memb.setRightEnd(groupDAO.findByName("otherchild").orElseThrow());
+        anyObject.add(memb);
+        anyObject = anyObjectDAO.save(anyObject);
+
+        APlainAttr attr = entityFactory.newEntity(APlainAttr.class);
+        attr.setSchema(plainSchemaDAO.findById("ctype").orElseThrow());
+        attr.add(validator, "otherchildctype", 
anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
+        attr.setOwner(anyObject);
+        attr.setMembership(anyObject.getMemberships().get(0));
+        anyObject.add(attr);
+        anyObjectDAO.save(anyObject);
+
+        results = searchDAO.search(cond, AnyTypeKind.ANY_OBJECT);
+        assertEquals(1, results.size());
+
+        assertTrue(results.stream().anyMatch(a -> 
"8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(a.getKey())));
+    }
+
     @Test
     public void issueSYNCOPE95() {
         groupDAO.findAll().forEach(group -> 
groupDAO.deleteById(group.getKey()));
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 b18c27c427..8bd268a6ab 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
@@ -240,7 +240,7 @@ public class GroupTest extends AbstractTest {
         List<Object> result = query.getResultList();
         return result.stream().
                 map(groupKey -> groupDAO.findById(groupKey.toString())).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 distinct().
                 collect(Collectors.toList());
     }
@@ -332,7 +332,7 @@ public class GroupTest extends AbstractTest {
         List<Object> result = query.getResultList();
         return result.stream().
                 map(groupKey -> groupDAO.findById(groupKey.toString())).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 distinct().
                 collect(Collectors.toList());
     }
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/AbstractDAO.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/AbstractDAO.java
index 082d235927..255abf7ab3 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/AbstractDAO.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/AbstractDAO.java
@@ -112,7 +112,7 @@ public abstract class AbstractDAO {
 
         return result.stream().
                 map(found -> findById(found.get(property).toString(), 
domainType, cache)).
-                filter(Optional::isPresent).map(Optional::get).map(n -> (E) 
n).toList();
+                flatMap(Optional::stream).map(n -> (E) n).toList();
     }
 
     protected void cascadeDelete(
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
index a3cba3abe9..0fd7c73516 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAnySearchDAO.java
@@ -100,7 +100,11 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
 
     }
 
-    protected static record QueryInfo(TextStringBuilder query, Set<String> 
fields, Set<PlainSchema> plainSchemas) {
+    protected static record QueryInfo(
+            TextStringBuilder query,
+            Set<String> fields,
+            Set<PlainSchema> plainSchemas,
+            List<Pair<String, PlainSchema>> membershipAttrConds) {
 
     }
 
@@ -754,6 +758,7 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO 
{
         TextStringBuilder query = new TextStringBuilder();
         Set<String> involvedFields = new HashSet<>();
         Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
+        List<Pair<String, PlainSchema>> membershipAttrConds = new 
ArrayList<>();
 
         switch (cond.getType()) {
             case LEAF, NOT_LEAF -> {
@@ -804,6 +809,13 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
                             Pair<String, PlainSchema> attrCondResult = 
getQuery(kind, leaf, not, parameters);
                             query.append(attrCondResult.getLeft());
                             
involvedPlainSchemas.add(attrCondResult.getRight());
+                            if (kind != AnyTypeKind.GROUP
+                                    && !not
+                                    && leaf.getType() != AttrCond.Type.ISNULL
+                                    && leaf.getType() != 
AttrCond.Type.ISNOTNULL) {
+
+                                membershipAttrConds.add(attrCondResult);
+                            }
                         }));
 
                 // allow for additional search conditions
@@ -813,10 +825,12 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
                 QueryInfo leftAndInfo = getQuery(kind, cond.getLeft(), 
parameters);
                 involvedFields.addAll(leftAndInfo.fields());
                 involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+                membershipAttrConds.addAll(leftAndInfo.membershipAttrConds());
 
                 QueryInfo rigthAndInfo = getQuery(kind, cond.getRight(), 
parameters);
                 involvedFields.addAll(rigthAndInfo.fields());
                 involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+                membershipAttrConds.addAll(rigthAndInfo.membershipAttrConds());
 
                 queryOp(query, "AND", leftAndInfo, rigthAndInfo);
             }
@@ -825,10 +839,12 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
                 QueryInfo leftOrInfo = getQuery(kind, cond.getLeft(), 
parameters);
                 involvedFields.addAll(leftOrInfo.fields());
                 involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+                membershipAttrConds.addAll(leftOrInfo.membershipAttrConds());
 
                 QueryInfo rigthOrInfo = getQuery(kind, cond.getRight(), 
parameters);
                 involvedFields.addAll(rigthOrInfo.fields());
                 involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+                membershipAttrConds.addAll(rigthOrInfo.membershipAttrConds());
 
                 queryOp(query, "OR", leftOrInfo, rigthOrInfo);
             }
@@ -837,7 +853,7 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO 
{
             }
         }
 
-        return new QueryInfo(query, involvedFields, involvedPlainSchemas);
+        return new QueryInfo(query, involvedFields, involvedPlainSchemas, 
membershipAttrConds);
     }
 
     protected void wrapQuery(
@@ -846,8 +862,6 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO 
{
             final AnyTypeKind kind,
             final String adminRealmsFilter) {
 
-        TextStringBuilder query = queryInfo.query();
-
         TextStringBuilder match = new TextStringBuilder("MATCH 
(n:").append(AnyRepoExt.node(kind)).append(") ").
                 append("WITH n.id AS id");
 
@@ -864,7 +878,7 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO 
{
         Stream.concat(
                 queryInfo.plainSchemas().stream(),
                 orderBy.stream().map(clause -> 
plainSchemaDAO.findById(clause.getProperty())).
-                        
filter(Optional::isPresent).map(Optional::get)).distinct().forEach(schema -> {
+                        flatMap(Optional::stream)).distinct().forEach(schema 
-> {
 
             match.append(", apoc.convert.getJsonProperty(n, 
'plainAttrs.").append(schema.getKey());
             if (schema.isUniqueConstraint()) {
@@ -875,6 +889,8 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO 
{
             match.append(" AS ").append(schema.getKey());
         });
 
+        TextStringBuilder query = queryInfo.query();
+
         // take realms into account
         if (query.startsWith("MATCH (n)")) {
             query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH 
(n)");
@@ -886,6 +902,75 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
         query.append(") AND EXISTS { ").append(adminRealmsFilter).append(" } 
");
     }
 
+    protected void membershipAttrConds(
+            final TextStringBuilder query,
+            final QueryInfo queryInfo,
+            final List<String> orderBy,
+            final AnyTypeKind kind) {
+
+        if (kind == AnyTypeKind.GROUP) {
+            return;
+        }
+        if (queryInfo.membershipAttrConds().isEmpty()) {
+            return;
+        }
+
+        Set<String> orderByItems = orderBy.stream().
+                map(clause -> StringUtils.substringBefore(clause, " ")).
+                collect(Collectors.toSet());
+
+        AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
+        Set<String> fields = Stream.concat(
+                queryInfo.fields().stream().filter(f -> !"id".equals(f)),
+                orderByItems.stream().filter(item -> !"id".equals(item) && 
anyUtils.getField(item).isPresent())).
+                collect(Collectors.toSet());
+
+        Set<PlainSchema> plainSchemas = Stream.concat(
+                queryInfo.membershipAttrConds().stream().map(Pair::getRight),
+                orderByItems.stream().map(item -> 
plainSchemaDAO.findById(item)).flatMap(Optional::stream)).
+                collect(Collectors.toSet());
+
+        // call
+        query.insert(0, "CALL () { ");
+
+        // return
+        TextStringBuilder returnStmt = new TextStringBuilder("RETURN id");
+
+        fields.forEach(f -> returnStmt.append(", ").append(f));
+
+        plainSchemas.forEach(schema -> returnStmt.append(", 
").append(schema.getKey()));
+
+        query.append(returnStmt);
+
+        // union
+        query.append(" UNION ").
+                append("MATCH (n:").append(AnyRepoExt.membNode(kind)).
+                append(")-[]-(m:").append(AnyRepoExt.node(kind) + ") ").
+                append("WITH m.id AS id ");
+
+        fields.forEach(f -> query.append(", m.").append(f).append(" AS 
").append(f));
+
+        plainSchemas.forEach(schema -> {
+            query.append(", apoc.convert.getJsonProperty(n, 
'plainAttrs.").append(schema.getKey());
+            if (schema.isUniqueConstraint()) {
+                query.append("', '$.uniqueValue')");
+            } else {
+                query.append("', '$.values')");
+            }
+            query.append(" AS ").append(schema.getKey());
+        });
+
+        query.append(" WHERE ");
+
+        query.append(queryInfo.membershipAttrConds().stream().
+                map(mac -> "(EXISTS { " + mac.getLeft() + "} )").
+                collect(Collectors.joining(" AND ")));
+
+        query.append(" AND EXISTS { (m)-[]-(r:Realm) WHERE r.id IN $param0 } 
").
+                append(returnStmt).
+                append(" } ");
+    }
+
     @Override
     protected long doCount(
             final Realm base,
@@ -906,15 +991,18 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
         wrapQuery(queryInfo, Streamable.empty(), kind, filter.filter());
         TextStringBuilder query = queryInfo.query();
 
-        // 3. prepare the count query
+        // 3. include membership plain attr queries
+        membershipAttrConds(query, queryInfo, List.of(), kind);
+
+        // 4. prepare the count query
         query.append("RETURN COUNT(id)");
 
         return neo4jTemplate.count(query.toString(), parameters);
     }
 
-    protected String parseOrderBy(
+    protected List<String> parseOrderBy(
             final AnyTypeKind kind,
-            final Stream<Sort.Order> orderBy) {
+            final Streamable<Sort.Order> orderBy) {
 
         AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
 
@@ -946,7 +1034,7 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
             }
         });
 
-        return clauses.stream().collect(Collectors.joining(", "));
+        return clauses;
     }
 
     @Override
@@ -970,9 +1058,15 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
             wrapQuery(queryInfo, pageable.getSort(), kind, filter.filter());
             TextStringBuilder query = queryInfo.query();
 
-            // 3. prepare the search query
+            List<String> orderBy = parseOrderBy(kind, pageable.getSort());
+            String orderByStmt = 
orderBy.stream().collect(Collectors.joining(", "));
+
+            // 3. include membership plain attr queries
+            membershipAttrConds(query, queryInfo, orderBy, kind);
+
+            // 4. prepare the search query
             query.append("RETURN id ").
-                    append("ORDER BY ").append(parseOrderBy(kind, 
pageable.getSort().get()));
+                    append("ORDER BY ").append(orderByStmt);
 
             if (pageable.isPaged()) {
                 query.append(" SKIP ").append(pageable.getPageSize() * 
pageable.getPageNumber()).
@@ -981,7 +1075,7 @@ public class Neo4jAnySearchDAO extends 
AbstractAnySearchDAO {
 
             LOG.debug("Query with auth and order by statements: {}, 
parameters: {}", query, parameters);
 
-            // 4. Prepare the result (avoiding duplicates)
+            // 5. Prepare the result (avoiding duplicates)
             return 
buildResult(neo4jClient.query(query.toString()).bindAll(parameters).fetch().all().stream().
                     map(found -> found.get("id")).toList(), kind);
         } catch (SyncopeClientException e) {
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
index 038886e6fd..8c8d9f2b4f 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jAuditEventDAO.java
@@ -183,6 +183,6 @@ public class Neo4jAuditEventDAO implements AuditEventDAO {
         return neo4jClient.query(query.toString()).
                 bindAll(parameters).fetch().all().stream().
                 map(found -> neo4jTemplate.findById(found.get("n.id"), 
Neo4jAuditEvent.class)).
-                
filter(Optional::isPresent).map(Optional::get).map(this::toAuditEventTO).toList();
+                flatMap(Optional::stream).map(this::toAuditEventTO).toList();
     }
 }
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AbstractAnyRepoExt.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AbstractAnyRepoExt.java
index c342094a1c..8857f06f90 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AbstractAnyRepoExt.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AbstractAnyRepoExt.java
@@ -364,17 +364,17 @@ public abstract class AbstractAnyRepoExt<A extends 
Any<?>, N extends AbstractAny
             if (reference.equals(PlainSchema.class)) {
                 atc.getPlainSchemas().stream().
                         map(schema -> 
plainSchemaDAO.findById(schema.getKey())).
-                        filter(Optional::isPresent).map(Optional::get).
+                        flatMap(Optional::stream).
                         forEach(schema -> result.getForSelf().add((S) schema));
             } else if (reference.equals(DerSchema.class)) {
                 atc.getDerSchemas().stream().
                         map(schema -> derSchemaDAO.findById(schema.getKey())).
-                        filter(Optional::isPresent).map(Optional::get).
+                        flatMap(Optional::stream).
                         forEach(schema -> result.getForSelf().add((S) schema));
             } else if (reference.equals(VirSchema.class)) {
                 atc.getVirSchemas().stream().
                         map(schema -> virSchemaDAO.findById(schema.getKey())).
-                        filter(Optional::isPresent).map(Optional::get).
+                        flatMap(Optional::stream).
                         forEach(schema -> result.getForSelf().add((S) schema));
             }
         });
@@ -419,17 +419,17 @@ public abstract class AbstractAnyRepoExt<A extends 
Any<?>, N extends AbstractAny
             if (reference.equals(PlainSchema.class)) {
                 atc.getPlainSchemas().stream().
                         map(schema -> 
plainSchemaDAO.findById(schema.getKey())).
-                        filter(Optional::isPresent).map(Optional::get).
+                        flatMap(Optional::stream).
                         forEach(schema -> 
result.getForMemberships().get(entry.getKey()).add((S) schema));
             } else if (reference.equals(DerSchema.class)) {
                 atc.getDerSchemas().stream().
                         map(schema -> derSchemaDAO.findById(schema.getKey())).
-                        filter(Optional::isPresent).map(Optional::get).
+                        flatMap(Optional::stream).
                         forEach(schema -> 
result.getForMemberships().get(entry.getKey()).add((S) schema));
             } else if (reference.equals(VirSchema.class)) {
                 atc.getVirSchemas().stream().
                         map(schema -> virSchemaDAO.findById(schema.getKey())).
-                        filter(Optional::isPresent).map(Optional::get).
+                        flatMap(Optional::stream).
                         forEach(schema -> 
result.getForMemberships().get(entry.getKey()).add((S) schema));
             }
         }));
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java
index 2d10177deb..8aa602c186 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyRepoExt.java
@@ -31,8 +31,10 @@ import 
org.apache.syncope.core.persistence.api.entity.PlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
 import org.apache.syncope.core.persistence.api.entity.Schema;
+import 
org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAMembership;
 import 
org.apache.syncope.core.persistence.neo4j.entity.anyobject.Neo4jAnyObject;
 import org.apache.syncope.core.persistence.neo4j.entity.group.Neo4jGroup;
+import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUMembership;
 import org.apache.syncope.core.persistence.neo4j.entity.user.Neo4jUser;
 
 public interface AnyRepoExt<A extends Any<?>> {
@@ -60,6 +62,17 @@ public interface AnyRepoExt<A extends Any<?>> {
         };
     }
 
+    static String membNode(final AnyTypeKind anyTypeKind) {
+        return switch (anyTypeKind) {
+            case USER ->
+                Neo4jUMembership.NODE;
+            case ANY_OBJECT ->
+                Neo4jAMembership.NODE;
+            default ->
+                "";
+        };
+    }
+
     List<A> findByKeys(List<String> keys);
 
     Optional<OffsetDateTime> findLastChange(String key);
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java
index b2c432fb46..8669192b97 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyTypeClassRepoExtImpl.java
@@ -186,17 +186,16 @@ public class AnyTypeClassRepoExtImpl extends AbstractDAO 
implements AnyTypeClass
             typeExt.getAuxClasses().remove(anyTypeClass);
 
             if (typeExt.getAuxClasses().isEmpty()) {
+                
groupCache.remove(EntityCacheKey.of(typeExt.getGroup().getKey()));
+
                 typeExt.getGroup().getTypeExtensions().remove(typeExt);
                 typeExt.setGroup(null);
-
-                
groupCache.remove(EntityCacheKey.of(typeExt.getGroup().getKey()));
             }
         }
 
         resourceDAO.findAll().stream().filter(resource -> 
resource.getProvisions().stream().
                 anyMatch(provision -> 
provision.getAuxClasses().contains(anyTypeClass.getKey()))).
                 forEach(resource -> {
-
                     resource.getProvisions().stream().
                             filter(provision -> 
provision.getAuxClasses().contains(anyTypeClass.getKey())).
                             forEach(provision -> 
provision.getAuxClasses().remove(anyTypeClass.getKey()));
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 f0a969591b..2c1a4789a1 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
@@ -227,7 +227,7 @@ public class GroupRepoExtImpl extends 
AbstractAnyRepoExt<Group, Neo4jGroup> impl
 
         return owned.stream().
                 map(id -> neo4jTemplate.findById(id, Neo4jGroup.class)).
-                
filter(Optional::isPresent).map(Optional::get).map(Group.class::cast).toList();
+                flatMap(Optional::stream).map(Group.class::cast).toList();
     }
 
     @Override
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
index d2e428f334..10c3d04353 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/AbstractGroupableRelatable.java
@@ -76,7 +76,7 @@ public abstract class AbstractGroupableRelatable<
     public Collection<? extends P> getPlainAttrs(final String plainSchema) {
         return 
Stream.concat(getPlainAttr(plainSchema).map(Stream::of).orElse(Stream.empty()),
                 memberships().stream().map(m -> m.getPlainAttr(plainSchema)).
-                        filter(Optional::isPresent).map(Optional::get)).
+                        flatMap(Optional::stream)).
                 toList();
     }
 
diff --git 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AnySearchTest.java
 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AnySearchTest.java
index 6efb2836f2..735d54cf33 100644
--- 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AnySearchTest.java
+++ 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/AnySearchTest.java
@@ -615,6 +615,8 @@ public class AnySearchTest extends AbstractTest {
         List<Sort.Order> orderByClauses = new ArrayList<>();
         orderByClauses.add(new Sort.Order(Sort.Direction.DESC, "username"));
         orderByClauses.add(new Sort.Order(Sort.Direction.ASC, "fullname"));
+        orderByClauses.add(new Sort.Order(Sort.Direction.ASC, "status"));
+        orderByClauses.add(new Sort.Order(Sort.Direction.DESC, "firstname"));
 
         List<User> users = searchDAO.search(searchCondition, orderByClauses, 
AnyTypeKind.USER);
         assertEquals(
diff --git 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/AnySearchTest.java
 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/AnySearchTest.java
index 7861038fd8..94fbff1e75 100644
--- 
a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/AnySearchTest.java
+++ 
b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/AnySearchTest.java
@@ -32,6 +32,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import 
org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
@@ -40,10 +41,14 @@ import 
org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
 import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
 import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
+import org.apache.syncope.core.persistence.api.entity.anyobject.APlainAttr;
+import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
@@ -66,6 +71,9 @@ public class AnySearchTest extends AbstractTest {
     @Autowired
     private GroupDAO groupDAO;
 
+    @Autowired
+    private AnyObjectDAO anyObjectDAO;
+
     @Autowired
     private AnySearchDAO searchDAO;
 
@@ -144,6 +152,55 @@ public class AnySearchTest extends AbstractTest {
         assertEquals(rossini.getKey(), users.get(0).getKey());
     }
 
+    @Test
+    public void searchByMembershipAttribute() {
+        AnyTypeCond typeCond = new AnyTypeCond();
+        typeCond.setAnyTypeKey("PRINTER");
+
+        AttrCond attrCond = new AttrCond(AttrCond.Type.EQ);
+        attrCond.setSchema("ctype");
+        attrCond.setExpression("otherchildctype");
+        SearchCond cond = SearchCond.getAnd(SearchCond.getLeaf(typeCond), 
SearchCond.getLeaf(attrCond));
+
+        long count = searchDAO.count(
+                
realmSearchDAO.findByFullPath(SyncopeConstants.ROOT_REALM).orElseThrow(),
+                true,
+                SyncopeConstants.FULL_ADMIN_REALMS,
+                cond,
+                AnyTypeKind.ANY_OBJECT);
+        assertEquals(0, count);
+        List<AnyObject> results = searchDAO.search(cond, 
AnyTypeKind.ANY_OBJECT);
+        assertTrue(results.isEmpty());
+
+        // add any object membership and its plain attribute
+        AnyObject anyObject = 
anyObjectDAO.findById("8559d14d-58c2-46eb-a2d4-a7d35161e8f8").orElseThrow();
+        AMembership memb = entityFactory.newEntity(AMembership.class);
+        memb.setLeftEnd(anyObject);
+        memb.setRightEnd(groupDAO.findByName("otherchild").orElseThrow());
+        anyObject.add(memb);
+        anyObject = anyObjectDAO.save(anyObject);
+
+        APlainAttr attr = entityFactory.newEntity(APlainAttr.class);
+        attr.setSchema(plainSchemaDAO.findById("ctype").orElseThrow());
+        attr.add(validator, "otherchildctype", 
anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
+        attr.setOwner(anyObject);
+        attr.setMembership(anyObject.getMemberships().get(0));
+        anyObject.add(attr);
+        anyObjectDAO.save(anyObject);
+
+        count = searchDAO.count(
+                
realmSearchDAO.findByFullPath(SyncopeConstants.ROOT_REALM).orElseThrow(),
+                true,
+                SyncopeConstants.FULL_ADMIN_REALMS,
+                cond,
+                AnyTypeKind.ANY_OBJECT);
+        assertEquals(1, count);
+        results = searchDAO.search(cond, AnyTypeKind.ANY_OBJECT);
+        assertEquals(1, results.size());
+
+        assertTrue(results.stream().anyMatch(a -> 
"8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(a.getKey())));
+    }
+
     @Test
     public void issueSYNCOPE95() {
         groupDAO.findAll().forEach(group -> 
groupDAO.deleteById(group.getKey()));
diff --git 
a/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml 
b/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml
index 233e9f3d25..5944661a80 100644
--- a/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-neo4j/src/test/resources/domains/MasterContent.xml
@@ -384,9 +384,10 @@ under the License.
                 creator="admin" lastModifier="admin" 
                 creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 
11:00:00"/>
   <SyncopeGroup_Realm left="f779c0d4-633b-4be5-8f57-32eb478a3ca5" 
right="e4c28e7a-9dbf-4ee7-9441-93812a0d4a28"/>
-  <TypeExtension id="88a71478-30aa-4ee0-8b2b-6cb32e7ba264"
-                 group_id="f779c0d4-633b-4be5-8f57-32eb478a3ca5" 
anyType_id="PRINTER"/>
-  <TypeExtension_AnyTypeClass 
typeExtension_id="88a71478-30aa-4ee0-8b2b-6cb32e7ba264" 
anyTypeClass_id="other"/>
+  <TypeExtension id="88a71478-30aa-4ee0-8b2b-6cb32e7ba264"/>
+  <TypeExtension_SyncopeGroup left="88a71478-30aa-4ee0-8b2b-6cb32e7ba264" 
right="f779c0d4-633b-4be5-8f57-32eb478a3ca5"/>
+  <TypeExtension_AnyType left="88a71478-30aa-4ee0-8b2b-6cb32e7ba264" 
right="PRINTER"/>
+  <TypeExtension_AnyTypeClass left="88a71478-30aa-4ee0-8b2b-6cb32e7ba264" 
right="other"/>
   <SyncopeGroup id="0cbcabd2-4410-4b6b-8f05-a052b451d18f" 
name="groupForWorkflowApproval"
                 creator="admin" lastModifier="admin" 
                 creationDate="2010-10-20 11:00:00" lastChangeDate="2010-10-20 
11:00:00"/>
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
index d884aab5fa..98bcc0b614 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/DefaultMappingManager.java
@@ -171,8 +171,7 @@ public class DefaultMappingManager implements 
MappingManager {
     protected List<Implementation> getTransformers(final Item item) {
         return item.getTransformers().stream().
                 map(implementationDAO::findById).
-                filter(Optional::isPresent).
-                map(Optional::get).
+                flatMap(Optional::stream).
                 collect(Collectors.toList());
     }
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
index 1ec1c99871..0ab69d0162 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AbstractAnyDataBinder.java
@@ -246,7 +246,7 @@ abstract class AbstractAnyDataBinder {
 
         Map<String, ConnObject> onResources = new HashMap<>();
 
-        
resources.stream().map(resourceDAO::findById).filter(Optional::isPresent).map(Optional::get).
+        
resources.stream().map(resourceDAO::findById).flatMap(Optional::stream).
                 forEach(resource -> 
resource.getProvisionByAnyType(any.getType().getKey()).
                 ifPresent(provision -> 
MappingUtils.getConnObjectKeyItem(provision).ifPresent(keyItem -> {
 
@@ -617,7 +617,7 @@ abstract class AbstractAnyDataBinder {
         any.getAuxClasses().clear();
         anyCR.getAuxClasses().stream().
                 map(className -> anyTypeClassDAO.findById(className)).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 forEach(auxClass -> {
                     if (auxClass == null) {
                         LOG.debug("Invalid " + 
AnyTypeClass.class.getSimpleName() + " {}, ignoring...", auxClass);
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index 66cb3af462..e240615bca 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -205,7 +205,7 @@ public class ResourceDataBinderImpl implements 
ResourceDataBinder {
                     Stream.concat(
                             anyType.getClasses().stream(),
                             
provision.getAuxClasses().stream().map(anyTypeClassDAO::findById).
-                                    
filter(Optional::isPresent).map(Optional::get)).forEach(anyTypeClass -> {
+                                    
flatMap(Optional::stream)).forEach(anyTypeClass -> {
 
                         
allowedSchemas.getPlainSchemas().addAll(anyTypeClass.getPlainSchemas().stream().
                                 map(PlainSchema::getKey).toList());
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
index bca2d34c1f..f7aae75b0f 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/DefaultPropagationManager.java
@@ -638,7 +638,7 @@ public class DefaultPropagationManager implements 
PropagationManager {
 
         
anyUtilsFactory.getInstance(kind).dao().findAllResourceKeys(key).stream().
                 map(resourceDAO::findById).
-                filter(Optional::isPresent).map(Optional::get).
+                flatMap(Optional::stream).
                 filter(resource -> 
!excludedResources.contains(resource.getKey())
                 && 
resource.getProvisionByAnyType(any.getType().getKey()).isPresent()
                 && resource.getPropagationPolicy() != null && 
resource.getPropagationPolicy().isUpdateDelta()).
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
index fda71b05d9..c2de716df7 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/LDAPMembershipPropagationActions.java
@@ -122,7 +122,7 @@ public class LDAPMembershipPropagationActions implements 
PropagationActions {
             // for each user group assigned to the resource of this task, 
compute and add the group's 
             // connector object link
             userDAO.findAllGroupKeys(user).stream().
-                    
map(groupDAO::findById).filter(Optional::isPresent).map(Optional::get).
+                    map(groupDAO::findById).flatMap(Optional::stream).
                     filter(group -> 
group.getResources().contains(taskInfo.getResource())).
                     forEach(group -> {
                         String groupConnObjectLink = 
evaluateGroupConnObjectLink(
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
index d774d6cebb..690332d0c0 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/InboundMatcher.java
@@ -223,8 +223,7 @@ public class InboundMatcher {
     protected List<Implementation> getTransformers(final Item item) {
         return item.getTransformers().stream().
                 map(implementationDAO::findById).
-                filter(Optional::isPresent).
-                map(Optional::get).
+                flatMap(Optional::stream).
                 collect(Collectors.toList());
     }
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
index a0e5cc69f1..229648f656 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePullJobDelegate.java
@@ -111,7 +111,7 @@ public class SinglePullJobDelegate extends PullJobDelegate 
implements SyncopeSin
             profile.setDryRun(false);
             
profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
             
profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream().
-                    
map(implementationDAO::findById).filter(Optional::isPresent).map(Optional::get).
+                    map(implementationDAO::findById).flatMap(Optional::stream).
                     toList()));
             profile.setExecutor(executor);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
index 12fba70677..a24cede99e 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/SinglePushJobDelegate.java
@@ -72,7 +72,7 @@ public class SinglePushJobDelegate extends PushJobDelegate 
implements SyncopeSin
         profile = new ProvisioningProfile<>(connector, task);
         profile.setExecutor(executor);
         
profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream().
-                
map(implementationDAO::findById).filter(Optional::isPresent).map(Optional::get).
+                map(implementationDAO::findById).flatMap(Optional::stream).
                 toList()));
         
profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
index 6e280a7337..fcc875f7df 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPullJobDelegate.java
@@ -186,7 +186,7 @@ public class StreamPullJobDelegate extends PullJobDelegate 
implements SyncopeStr
             profile.setDryRun(false);
             profile.setConflictResolutionAction(conflictResolutionAction);
             
profile.getActions().addAll(getPullActions(pullTaskTO.getActions().stream().
-                    
map(implementationDAO::findById).filter(Optional::isPresent).map(Optional::get).
+                    map(implementationDAO::findById).flatMap(Optional::stream).
                     toList()));
             profile.setExecutor(executor);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
index dfd95b72ab..7b6b760f35 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/stream/StreamPushJobDelegate.java
@@ -141,7 +141,7 @@ public class StreamPushJobDelegate extends PushJobDelegate 
implements SyncopeStr
             profile = new ProvisioningProfile<>(connector, task);
             profile.setExecutor(executor);
             
profile.getActions().addAll(getPushActions(pushTaskTO.getActions().stream().
-                    
map(implementationDAO::findById).filter(Optional::isPresent).map(Optional::get).
+                    map(implementationDAO::findById).flatMap(Optional::stream).
                     toList()));
             
profile.setConflictResolutionAction(ConflictResolutionAction.FIRSTMATCH);
 
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
index b8ca9fd36c..d180a325d7 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/utils/ConnObjectUtils.java
@@ -202,7 +202,7 @@ public class ConnObjectUtils {
             // add resource policies
             userCR.getResources().stream().
                     map(resourceDAO::findById).
-                    filter(Optional::isPresent).map(Optional::get).
+                    flatMap(Optional::stream).
                     filter(r -> r.getPasswordPolicy() != null).
                     forEach(r -> passwordPolicies.add(r.getPasswordPolicy()));
 
diff --git 
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
 
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
index c19a2898fe..00b645e90c 100644
--- 
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
+++ 
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractAnyObjectWorkflowAdapter.java
@@ -111,7 +111,7 @@ public abstract class AbstractAnyObjectWorkflowAdapter
 
         // finally publish events for all groups affected by this operation, 
via membership
         
result.getResult().getMemberships().stream().map(MembershipUR::getGroup).distinct().
-                
map(groupDAO::findById).filter(Optional::isPresent).map(Optional::get).
+                map(groupDAO::findById).flatMap(Optional::stream).
                 forEach(group -> publisher.publishEvent(new 
EntityLifecycleEvent<>(
                 this, SyncDeltaType.UPDATE, group, 
AuthContextUtils.getDomain())));
 
diff --git 
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
 
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
index b1f6966962..3c8228ef26 100644
--- 
a/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
+++ 
b/core/workflow-java/src/main/java/org/apache/syncope/core/workflow/java/AbstractUserWorkflowAdapter.java
@@ -345,7 +345,7 @@ public abstract class AbstractUserWorkflowAdapter extends 
AbstractWorkflowAdapte
 
         // finally publish events for all groups affected by this operation, 
via membership
         
result.getResult().getLeft().getMemberships().stream().map(MembershipUR::getGroup).distinct().
-                
map(groupDAO::findById).filter(Optional::isPresent).map(Optional::get).
+                map(groupDAO::findById).flatMap(Optional::stream).
                 forEach(group -> publisher.publishEvent(new 
EntityLifecycleEvent<>(
                 this, SyncDeltaType.UPDATE, group, 
AuthContextUtils.getDomain())));
 
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 621f0723ac..0f9658f078 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
@@ -36,14 +36,11 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.AuditEvent;
-import org.apache.syncope.core.persistence.api.entity.GroupablePlainAttr;
 import org.apache.syncope.core.persistence.api.entity.GroupableRelatable;
-import org.apache.syncope.core.persistence.api.entity.Membership;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.apache.syncope.core.persistence.api.entity.Relationship;
 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;
@@ -208,11 +205,10 @@ public class ElasticsearchUtils {
 
         // add also flattened membership attributes
         if (any instanceof GroupableRelatable) {
-            GroupableRelatable<? extends Any, ? extends Membership, ? extends 
GroupablePlainAttr, ? extends Any, ? 
-                    extends Relationship> entity = 
GroupableRelatable.class.cast(any);
-            entity.getMemberships().forEach(m -> 
entity.getPlainAttrs(m).forEach(mAttr -> {
-                List<Object> values = 
mAttr.getValues().stream().map(PlainAttrValue::getValue)
-                        .collect(Collectors.toList());
+            GroupableRelatable<?, ?, ?, ?, ?> groupable = 
GroupableRelatable.class.cast(any);
+            groupable.getMemberships().forEach(m -> 
groupable.getPlainAttrs(m).forEach(mAttr -> {
+                List<Object> values = mAttr.getValues().stream().
+                        
map(PlainAttrValue::getValue).collect(Collectors.toList());
 
                 Optional.ofNullable(mAttr.getUniqueValue()).ifPresent(v -> 
values.add(v.getValue()));
 
@@ -226,7 +222,7 @@ public class ElasticsearchUtils {
                 }
             }));
         }
-        
+
         return builder;
     }
 
diff --git 
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchRealmSearchDAO.java
 
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchRealmSearchDAO.java
index 2155d6dc67..2208ad5c6a 100644
--- 
a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchRealmSearchDAO.java
+++ 
b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchRealmSearchDAO.java
@@ -127,7 +127,7 @@ public class ElasticsearchRealmSearchDAO implements 
RealmSearchDAO {
                 new Query.Builder().term(QueryBuilders.term().
                         field("name").value(name).build()).build());
         return result.stream().map(realmDAO::findById).
-                
filter(Optional::isPresent).map(Optional::get).map(Realm.class::cast).toList();
+                flatMap(Optional::stream).map(Realm.class::cast).toList();
     }
 
     @Override
@@ -136,7 +136,7 @@ public class ElasticsearchRealmSearchDAO implements 
RealmSearchDAO {
                 new Query.Builder().term(QueryBuilders.term().
                         
field("parent_id").value(realm.getKey()).build()).build());
         return result.stream().map(realmDAO::findById).
-                
filter(Optional::isPresent).map(Optional::get).map(Realm.class::cast).toList();
+                flatMap(Optional::stream).map(Realm.class::cast).toList();
     }
 
     protected Query buildDescendantQuery(final String base, final String 
keyword) {
@@ -209,7 +209,7 @@ public class ElasticsearchRealmSearchDAO implements 
RealmSearchDAO {
         }
 
         return result.stream().map(realmDAO::findById).
-                
filter(Optional::isPresent).map(Optional::get).map(Realm.class::cast).toList();
+                flatMap(Optional::stream).map(Realm.class::cast).toList();
     }
 
     @Override
diff --git 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
index 8e689dce91..3b98bf5bd5 100644
--- 
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
+++ 
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCUserManager.java
@@ -130,8 +130,7 @@ public class OIDCUserManager {
     protected List<Implementation> getTransformers(final Item item) {
         return item.getTransformers().stream().
                 map(implementationDAO::findById).
-                filter(Optional::isPresent).
-                map(Optional::get).
+                flatMap(Optional::stream).
                 collect(Collectors.toList());
     }
 
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 6ef66d10ae..367b648110 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
@@ -36,14 +36,11 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.entity.Any;
 import org.apache.syncope.core.persistence.api.entity.AnyTypeClass;
 import org.apache.syncope.core.persistence.api.entity.AuditEvent;
-import org.apache.syncope.core.persistence.api.entity.GroupablePlainAttr;
 import org.apache.syncope.core.persistence.api.entity.GroupableRelatable;
-import org.apache.syncope.core.persistence.api.entity.Membership;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
 import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.apache.syncope.core.persistence.api.entity.Relationship;
 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;
@@ -208,11 +205,10 @@ public class OpenSearchUtils {
 
         // add also flattened membership attributes
         if (any instanceof GroupableRelatable) {
-            GroupableRelatable<? extends Any, ? extends Membership, ? extends 
GroupablePlainAttr, ? extends Any, ?
-                    extends Relationship> entity = 
GroupableRelatable.class.cast(any);
-            entity.getMemberships().forEach(m -> 
entity.getPlainAttrs(m).forEach(mAttr -> {
-                List<Object> values = 
mAttr.getValues().stream().map(PlainAttrValue::getValue)
-                        .collect(Collectors.toList());
+            GroupableRelatable<?, ?, ?, ?, ?> groupable = 
GroupableRelatable.class.cast(any);
+            groupable.getMemberships().forEach(m -> 
groupable.getPlainAttrs(m).forEach(mAttr -> {
+                List<Object> values = mAttr.getValues().stream().
+                        
map(PlainAttrValue::getValue).collect(Collectors.toList());
 
                 Optional.ofNullable(mAttr.getUniqueValue()).ifPresent(v -> 
values.add(v.getValue()));
 
@@ -226,7 +222,7 @@ public class OpenSearchUtils {
                 }
             }));
         }
-        
+
         return builder;
     }
 
diff --git 
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchRealmSearchDAO.java
 
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchRealmSearchDAO.java
index 5cd31034a1..d882119faa 100644
--- 
a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchRealmSearchDAO.java
+++ 
b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchRealmSearchDAO.java
@@ -127,7 +127,7 @@ public class OpenSearchRealmSearchDAO implements 
RealmSearchDAO {
                 new Query.Builder().term(QueryBuilders.term().
                         
field("name").value(FieldValue.of(name)).build()).build());
         return result.stream().map(realmDAO::findById).
-                
filter(Optional::isPresent).map(Optional::get).map(Realm.class::cast).toList();
+                flatMap(Optional::stream).map(Realm.class::cast).toList();
     }
 
     @Override
@@ -136,7 +136,7 @@ public class OpenSearchRealmSearchDAO implements 
RealmSearchDAO {
                 new Query.Builder().term(QueryBuilders.term().
                         
field("parent_id").value(FieldValue.of(realm.getKey())).build()).build());
         return result.stream().map(realmDAO::findById).
-                
filter(Optional::isPresent).map(Optional::get).map(Realm.class::cast).toList();
+                flatMap(Optional::stream).map(Realm.class::cast).toList();
     }
 
     protected Query buildDescendantQuery(final String base, final String 
keyword) {
@@ -209,7 +209,7 @@ public class OpenSearchRealmSearchDAO implements 
RealmSearchDAO {
         }
 
         return result.stream().map(realmDAO::findById).
-                
filter(Optional::isPresent).map(Optional::get).map(Realm.class::cast).toList();
+                flatMap(Optional::stream).map(Realm.class::cast).toList();
     }
 
     @Override
diff --git 
a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java
 
b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java
index 45274ecb0c..1b1c121d90 100644
--- 
a/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java
+++ 
b/ext/saml2sp4ui/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2SP4UIUserManager.java
@@ -142,8 +142,7 @@ public class SAML2SP4UIUserManager {
     protected List<Implementation> getTransformers(final Item item) {
         return item.getTransformers().stream().
                 map(implementationDAO::findById).
-                filter(Optional::isPresent).
-                map(Optional::get).
+                flatMap(Optional::stream).
                 collect(Collectors.toList());
     }
 
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
index 4609efbf09..ec9247e9d6 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.fit.core;
 
-import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -354,7 +353,7 @@ public class SearchITCase extends AbstractITCase {
         PagedResult<UserTO> issueSYNCOPE1321 = USER_SERVICE.search(new 
AnyQuery.Builder().
                 realm(SyncopeConstants.ROOT_REALM).
                 fiql(SyncopeClient.getUserSearchConditionBuilder().
-                        
is("lastLoginDate").lexicalNotBefore("2016-03-02T15:21:22%2B0300").
+                        
is("lastChangeDate").lexicalNotBefore("2010-03-02T15:21:22%2B0300").
                         and("username").equalTo("bellini").query()).
                 build());
         assertEquals(users, issueSYNCOPE1321);
@@ -623,6 +622,95 @@ public class SearchITCase extends AbstractITCase {
         assertTrue(users > 0);
     }
 
+    @Test
+    public void userByMembershipAttribute() {
+        // create type extension for the 'employee' group, if not present
+        GroupTO employee = GROUP_SERVICE.read("employee");
+        if (employee.getTypeExtension(AnyTypeKind.USER.name()).isEmpty()) {
+            TypeExtensionTO typeExtensionTO = new TypeExtensionTO();
+            typeExtensionTO.setAnyType(AnyTypeKind.USER.name());
+            typeExtensionTO.getAuxClasses().add("other");
+            updateGroup(new 
GroupUR.Builder(employee.getKey()).typeExtension(typeExtensionTO).build());
+        }
+
+        if (IS_EXT_SEARCH_ENABLED) {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+
+        PagedResult<UserTO> matching = USER_SERVICE.search(
+                new 
AnyQuery.Builder().fiql(SyncopeClient.getUserSearchConditionBuilder().
+                        
is("ctype").equalTo("additionalctype").query()).build());
+        assertEquals(0, matching.getTotalCount());
+        matching = USER_SERVICE.search(
+                new 
AnyQuery.Builder().fiql(SyncopeClient.getUserSearchConditionBuilder().
+                        is("ctype").equalTo("myownctype").query()).build());
+        assertEquals(0, matching.getTotalCount());
+
+        // add user membership and its plain attribute
+        updateUser(new UserUR.Builder(USER_SERVICE.read("puccini").getKey())
+                .plainAttr(attrAddReplacePatch("ctype", "myownctype"))
+                .membership(new 
MembershipUR.Builder(GROUP_SERVICE.read("additional").getKey()).
+                        plainAttrs(attr("ctype", "additionalctype")).build())
+                .membership(new MembershipUR.Builder(employee.getKey())
+                        .plainAttrs(attr("ctype", 
"additionalemployeectype")).build())
+                .build());
+
+        if (IS_EXT_SEARCH_ENABLED) {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+
+        matching = USER_SERVICE.search(
+                new 
AnyQuery.Builder().fiql(SyncopeClient.getUserSearchConditionBuilder().
+                        
is("ctype").equalTo("additionalctype").query()).build());
+        assertEquals(1, matching.getTotalCount());
+        assertTrue(matching.getResult().stream().anyMatch(u -> 
"puccini".equals(u.getUsername())));
+
+        // check also that search on user plain attribute (not in membership) 
works
+        matching = USER_SERVICE.search(
+                new 
AnyQuery.Builder().fiql(SyncopeClient.getUserSearchConditionBuilder().
+                        is("ctype").equalTo("myownctype").query()).build());
+        assertEquals(1, matching.getTotalCount());
+        assertTrue(matching.getResult().stream().anyMatch(u -> 
"puccini".equals(u.getUsername())));
+    }
+
+    @Test
+    public void anyObjectByMembershipAttribute() {
+        PagedResult<AnyObjectTO> matching = ANY_OBJECT_SERVICE.search(
+                new 
AnyQuery.Builder().fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER)
+                        
.is("ctype").equalTo("otherchildctype").query()).build());
+        assertEquals(0, matching.getTotalCount());
+
+        // add any object membership and its plain attribute
+        updateAnyObject(new 
AnyObjectUR.Builder("8559d14d-58c2-46eb-a2d4-a7d35161e8f8").
+                membership(new 
MembershipUR.Builder(GROUP_SERVICE.read("otherchild").getKey()).
+                        plainAttrs(attr("ctype", "otherchildctype")).
+                        build()).build());
+
+        if (IS_EXT_SEARCH_ENABLED) {
+            try {
+                Thread.sleep(2000);
+            } catch (InterruptedException ex) {
+                // ignore
+            }
+        }
+
+        matching = ANY_OBJECT_SERVICE.search(
+                new 
AnyQuery.Builder().fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER)
+                        
.is("ctype").equalTo("otherchildctype").query()).build());
+        assertEquals(1, matching.getTotalCount());
+
+        assertTrue(matching.getResult().stream().
+                anyMatch(a -> 
"8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(a.getKey())));
+    }
+
     @Test
     public void issueSYNCOPE768() {
         long usersWithNullable = USER_SERVICE.search(new 
AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
@@ -1060,57 +1148,4 @@ public class SearchITCase extends AbstractITCase {
             deleteUser("user test 182");
         }
     }
-
-    @Test
-    void userByMembershipAttribute() {
-        // search user by membership attribute
-        UserTO puccini = USER_SERVICE.read("puccini");
-        GroupTO additional = GROUP_SERVICE.read("additional");
-        GroupTO employee = GROUP_SERVICE.read("employee");
-        TypeExtensionTO typeExtensionTO = new TypeExtensionTO();
-        typeExtensionTO.setAnyType(AnyTypeKind.USER.name());
-        typeExtensionTO.getAuxClasses().add("other");
-        updateGroup(new 
GroupUR.Builder(employee.getKey()).typeExtension(typeExtensionTO).build());
-        // add a membership and its plain attribute
-        updateUser(new UserUR.Builder(puccini.getKey())
-                .plainAttr(attrAddReplacePatch("ctype", "myownctype"))
-                .memberships(
-                new 
MembershipUR.Builder(additional.getKey()).plainAttrs(attr("ctype", 
"additionalctype"))
-                        .build(), new MembershipUR.Builder(employee.getKey())
-                                .plainAttrs(attr("ctype", 
"additionalemployeectype"))
-                                .build()).build());
-        await().until(() -> USER_SERVICE.search(new 
AnyQuery.Builder().page(1).size(10)
-                
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("additionalctype").query())
-                .build()).getTotalCount() == 1);
-        assertTrue(USER_SERVICE.search(new AnyQuery.Builder().page(1).size(10)
-                
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("additionalctype").query())
-                .build()).getResult().stream().anyMatch(u -> 
"puccini".equals(u.getUsername())));
-        assertTrue(USER_SERVICE.search(new AnyQuery.Builder().page(1).size(10)
-                
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("additionalemployeectype")
-                        .query()).build()).getResult().stream().anyMatch(u -> 
"puccini".equals(u.getUsername())));
-        // check also that search on user plain attribute (not in membership) 
works
-        assertTrue(USER_SERVICE.search(new AnyQuery.Builder().page(1).size(10)
-                
.fiql(SyncopeClient.getUserSearchConditionBuilder().is("ctype").equalTo("myownctype").query())
-                .build()).getResult().stream().anyMatch(u -> 
"puccini".equals(u.getUsername())));
-    }
-    
-    @Test
-    void anyObjectByMembershipAttribute() {
-        // search user by membership attribute
-        AnyObjectTO canonMf = 
ANY_OBJECT_SERVICE.read("8559d14d-58c2-46eb-a2d4-a7d35161e8f8");
-        GroupTO otherchild = GROUP_SERVICE.read("otherchild");
-        // add a membership and its plain attribute
-        updateAnyObject(new AnyObjectUR.Builder(canonMf.getKey()).memberships(
-                new 
MembershipUR.Builder(otherchild.getKey()).plainAttrs(attr("ctype", 
"otherchildctype"))
-                        .build()).build());
-        await().until(() -> ANY_OBJECT_SERVICE.search(new 
AnyQuery.Builder().page(1).size(10)
-                
.fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("ctype").equalTo("otherchildctype")
-                        .query()).build()).getTotalCount() == 1);
-        assertTrue(ANY_OBJECT_SERVICE.search(new 
AnyQuery.Builder().page(1).size(10)
-                        
.fiql(SyncopeClient.getAnyObjectSearchConditionBuilder(PRINTER).is("ctype").equalTo(
-                                        "otherchildctype")
-                                .query()).build()).getResult().stream()
-                .anyMatch(u -> 
"8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(u.getKey())));
-    }
-
 }
diff --git a/pom.xml b/pom.xml
index 085cb4584c..3b8c158bbd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -495,7 +495,7 @@ under the License.
     <cargo.deployable.ping.timeout>60000</cargo.deployable.ping.timeout>
 
     <tomcat.version>10.1.31</tomcat.version>
-    <wildfly.version>33.0.2.Final</wildfly.version>
+    <wildfly.version>34.0.0.Final</wildfly.version>
     <payara.version>6.2024.10</payara.version>
     <jakarta.faces.version>4.1.1</jakarta.faces.version>
 
@@ -503,7 +503,7 @@ under the License.
     <docker.mysql.version>9.0</docker.mysql.version>
     <docker.mariadb.version>11</docker.mariadb.version>
     <docker.oracle.version>23-slim-faststart</docker.oracle.version>
-    <docker.neo4j.version>5.23.0</docker.neo4j.version>
+    <docker.neo4j.version>5.24.2</docker.neo4j.version>
 
     <jdbc.postgresql.version>42.7.4</jdbc.postgresql.version>
     <jdbc.mysql.version>9.1.0</jdbc.mysql.version>
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAuthMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAuthMapper.java
index f35e642654..3f925e9373 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAuthMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/DefaultAuthMapper.java
@@ -99,8 +99,7 @@ public class DefaultAuthMapper implements AuthMapper {
         if (!mfaAuthHandlers.isEmpty()) {
             Set<String> fns = mfaAuthHandlers.stream().
                     map(handler -> authModules.stream().filter(am -> 
handler.equals(am.getKey())).findFirst()).
-                    filter(Optional::isPresent).
-                    map(Optional::get).
+                    flatMap(Optional::stream).
                     filter(am -> am.getConf() instanceof MFAAuthModuleConf).
                     map(am -> ((MFAAuthModuleConf) 
am.getConf()).getFriendlyName()).
                     collect(Collectors.toSet());

Reply via email to