You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2026/02/24 15:32:48 UTC
(syncope) branch master updated: [SYNCOPE-1951] Realm search based on plain attrs (#1300)
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 377f1cd775 [SYNCOPE-1951] Realm search based on plain attrs (#1300)
377f1cd775 is described below
commit 377f1cd775471f960858b7fea2d68b250d6b7379
Author: Matteo Tatoni <ta...@gmail.com>
AuthorDate: Tue Feb 24 11:52:46 2026 +0100
[SYNCOPE-1951] Realm search based on plain attrs (#1300)
---
.../clientapps/ClientAppModalPanelBuilder.java | 2 +-
.../console/panels/ConnObjectListViewPanel.java | 3 +-
.../client/console/status/ReconTaskPanel.java | 2 +-
.../wizards/resources/ConnectorDetailsPanel.java | 2 +-
.../client/console/commons/RealmsUtils.java | 4 +-
.../client/console/panels/RealmChoicePanel.java | 4 +-
.../console/tasks/SchedTaskWizardBuilder.java | 2 +-
.../client/console/wizards/any/Details.java | 2 +-
.../console/wizards/role/RoleWizardBuilder.java | 2 +-
.../apache/syncope/client/lib/SyncopeClient.java | 10 +
.../common/lib/search/RealmCompleteCondition.java | 22 +
.../search/RealmFiqlSearchConditionBuilder.java | 50 ++
.../common/lib/search/RealmPartialCondition.java | 22 +
.../syncope/common/lib/search/RealmProperty.java | 22 +
.../syncope/common/rest/api/beans/AnyQuery.java | 4 +-
.../syncope/common/rest/api/beans/RealmQuery.java | 30 +-
.../syncope/core/logic/ReconciliationLogic.java | 2 +-
.../syncope/core/rest/cxf/IdMRESTCXFContext.java | 4 +-
.../cxf/service/ReconciliationServiceImpl.java | 10 +-
.../org/apache/syncope/core/logic/GroupLogic.java | 2 +-
.../org/apache/syncope/core/logic/RealmLogic.java | 11 +-
.../org/apache/syncope/core/logic/UserLogic.java | 2 +-
.../core/rest/cxf/IdRepoRESTCXFContext.java | 13 +-
.../core/rest/cxf/SyncopeOpenApiCustomizer.java | 4 +-
.../core/rest/cxf/service/AbstractAnyService.java | 9 +-
.../rest/cxf/service/AbstractSearchService.java | 11 +-
.../rest/cxf/service/AnyObjectServiceImpl.java | 4 +-
.../core/rest/cxf/service/GroupServiceImpl.java | 4 +-
.../core/rest/cxf/service/RealmServiceImpl.java | 30 +-
.../core/rest/cxf/service/UserServiceImpl.java | 4 +-
.../rest/cxf/service/AnyObjectServiceTest.java | 4 +-
.../syncope/core/persistence/api/dao/AnyDAO.java | 13 -
.../core/persistence/api/dao/AnySearchDAO.java | 36 +-
.../core/persistence/api/dao/RealmSearchDAO.java | 36 +-
.../api/search/AnySearchCondVisitor.java | 31 +
.../api/search/RealmSearchCondVisitor.java | 31 +
.../api/search/SearchCondConverter.java | 7 +-
....java => SyncopeAbstractSearchCondVisitor.java} | 22 +-
.../api/search/SearchCondConverterTest.java | 2 +-
.../common/CommonPersistenceContext.java | 21 +-
.../common/dao/AbstractAnySearchDAO.java | 193 +------
.../common/dao/AbstractRealmSearchDAO.java | 110 ++++
.../persistence/common/dao/AbstractSearchDAO.java | 312 ++++++++++
.../core/persistence/common/dao/AnyFinder.java | 155 -----
.../persistence/jpa/MariaDBPersistenceContext.java | 26 +-
.../persistence/jpa/MySQLPersistenceContext.java | 26 +-
.../persistence/jpa/OraclePersistenceContext.java | 26 +-
.../core/persistence/jpa/PGPersistenceContext.java | 26 +-
.../core/persistence/jpa/PersistenceContext.java | 26 +-
.../jpa/content/XMLContentExporter.java | 3 +-
.../jpa/dao/AbstractJPAAnySearchDAO.java | 171 +-----
.../jpa/dao/AbstractJPARealmSearchDAO.java | 541 ++++++++++++++++++
.../core/persistence/jpa/dao/JPARealmDAO.java | 2 +-
.../persistence/jpa/dao/JPARealmSearchDAO.java | 190 -------
.../core/persistence/jpa/dao/JPATaskDAO.java | 2 +-
.../jpa/dao/MariaDBJPAAnySearchDAO.java | 3 -
...earchDAO.java => MariaDBJPARealmSearchDAO.java} | 30 +-
.../persistence/jpa/dao/MySQLJPAAnySearchDAO.java | 3 -
...ySearchDAO.java => MySQLJPARealmSearchDAO.java} | 170 +++---
.../persistence/jpa/dao/OracleJPAAnySearchDAO.java | 3 -
...SearchDAO.java => OracleJPARealmSearchDAO.java} | 190 ++-----
.../persistence/jpa/dao/PGJPAAnySearchDAO.java | 3 -
...AAnySearchDAO.java => PGJPARealmSearchDAO.java} | 187 +++---
.../core/persistence/jpa/dao/RealmSearchNode.java | 141 +++++
.../jpa/dao/repo/AbstractAnyRepoExt.java | 11 -
.../jpa/dao/repo/AnyObjectRepoExtImpl.java | 5 +-
.../core/persistence/jpa/dao/repo/AnyRepoExt.java | 3 -
.../persistence/jpa/dao/repo/GroupRepoExtImpl.java | 5 +-
.../persistence/jpa/dao/repo/UserRepoExtImpl.java | 5 +-
.../core/persistence/jpa/entity/JPASRARoute.java | 4 +-
.../core/persistence/jpa/inner/AnySearchTest.java | 37 +-
.../persistence/jpa/inner/MultitenancyTest.java | 5 +-
.../core/persistence/jpa/inner/RealmTest.java | 4 +-
.../core/persistence/jpa/inner/UserTest.java | 31 -
.../core/persistence/jpa/outer/AnySearchTest.java | 42 ++
.../core/persistence/jpa/outer/RealmTest.java | 130 +++++
.../core/persistence/jpa/outer/UserTest.java | 15 +-
.../core/persistence/neo4j/PersistenceContext.java | 32 +-
.../persistence/neo4j/dao/Neo4jAnySearchDAO.java | 47 +-
.../core/persistence/neo4j/dao/Neo4jRealmDAO.java | 2 +-
.../persistence/neo4j/dao/Neo4jRealmSearchDAO.java | 631 +++++++++++++++++++--
.../core/persistence/neo4j/dao/Neo4jTaskDAO.java | 2 +-
.../neo4j/dao/repo/AbstractAnyRepoExt.java | 10 -
.../neo4j/dao/repo/AnyObjectRepoExtImpl.java | 3 -
.../persistence/neo4j/dao/repo/AnyRepoExt.java | 2 -
.../neo4j/dao/repo/GroupRepoExtImpl.java | 5 +-
.../neo4j/dao/repo/RoleRepoExtImpl.java | 5 -
.../neo4j/dao/repo/UserRepoExtImpl.java | 3 -
.../persistence/neo4j/entity/Neo4jSRARoute.java | 4 +-
.../persistence/neo4j/inner/AnySearchTest.java | 37 +-
.../persistence/neo4j/inner/MultitenancyTest.java | 5 +-
.../core/persistence/neo4j/inner/RealmTest.java | 13 +-
.../core/persistence/neo4j/inner/UserTest.java | 31 -
.../persistence/neo4j/outer/AnySearchTest.java | 40 ++
.../core/persistence/neo4j/outer/RealmTest.java | 126 ++++
.../core/persistence/neo4j/outer/UserTest.java | 15 +-
.../provisioning/java/ProvisioningContext.java | 15 +-
.../java/data/FIQLQueryDataBinderImpl.java | 6 +-
.../java/data/GroupDataBinderImpl.java | 6 +-
.../provisioning/java/data/RoleDataBinderImpl.java | 7 +-
.../notification/DefaultNotificationManager.java | 6 +-
.../provisioning/java/pushpull/InboundMatcher.java | 86 ++-
.../java/pushpull/PushJobDelegate.java | 11 +-
.../syncope/core/starter/SelfKeymasterContext.java | 2 +-
.../elasticsearch/client/ElasticsearchUtils.java | 25 +-
.../ElasticsearchPersistenceContext.java | 15 +-
.../dao/ElasticsearchAnySearchDAO.java | 12 +-
.../dao/ElasticsearchRealmSearchDAO.java | 314 ++++++++--
.../dao/ElasticsearchAnySearchDAOTest.java | 3 +-
.../dao/ElasticsearchRealmSearchDAOTest.java | 216 +++++++
.../ext/opensearch/client/OpenSearchUtils.java | 25 +-
.../opensearch/OpenSearchPersistenceContext.java | 15 +-
.../opensearch/dao/OpenSearchAnySearchDAO.java | 12 +-
.../opensearch/dao/OpenSearchRealmSearchDAO.java | 317 +++++++++--
.../opensearch/dao/OpenSearchAnySearchDAOTest.java | 3 +-
.../dao/OpenSearchRealmSearchDAOTest.java | 216 +++++++
.../syncope/fit/core/reference/TestCommand.java | 2 +-
.../org/apache/syncope/fit/AbstractITCase.java | 6 +-
.../syncope/fit/core/MultitenancyITCase.java | 4 +-
.../org/apache/syncope/fit/core/RealmITCase.java | 165 +++++-
.../org/apache/syncope/fit/core/UserITCase.java | 2 +-
.../apache/syncope/fit/core/UserIssuesITCase.java | 7 +-
122 files changed, 4228 insertions(+), 1615 deletions(-)
diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
index 7c2e2532b0..10044edcd2 100644
--- a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
+++ b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
@@ -196,7 +196,7 @@ public class ClientAppModalPanelBuilder<T extends ClientAppTO> extends AbstractM
protected Iterator<String> getChoices(final String input) {
return realmRestClient.search(fullRealmsTree
? RealmsUtils.buildBaseQuery()
- : RealmsUtils.buildKeywordQuery(input)).getResult().stream().
+ : RealmsUtils.buildNameQuery(input)).getResult().stream().
map(RealmTO::getFullPath).iterator();
}
};
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java
index b305b96819..b059363ee5 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnObjectListViewPanel.java
@@ -161,8 +161,7 @@ public abstract class ConnObjectListViewPanel extends Panel {
List<ConnObject> listOfItems = reloadItems(resource.getKey(), anyType, null, null);
- ListViewPanel.Builder<ConnObject> builder = new ListViewPanel.Builder<>(
- ConnObject.class, pageRef) {
+ ListViewPanel.Builder<ConnObject> builder = new ListViewPanel.Builder<>(ConnObject.class, pageRef) {
private static final long serialVersionUID = -8251750413385566738L;
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
index 9851476317..0f656fd100 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ReconTaskPanel.java
@@ -141,7 +141,7 @@ public class ReconTaskPanel extends MultilevelPanel.SecondLevel {
return (RealmsUtils.checkInput(input)
? (realmRestClient.search(fullRealmsTree
? RealmsUtils.buildBaseQuery()
- : RealmsUtils.buildKeywordQuery(input)).getResult())
+ : RealmsUtils.buildNameQuery(input)).getResult())
: List.<RealmTO>of()).stream().
map(RealmTO::getFullPath).iterator();
}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
index 1918fa55d6..90274ea233 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
@@ -69,7 +69,7 @@ public class ConnectorDetailsPanel extends WizardStep {
return (RealmsUtils.checkInput(input)
? (realmRestClient.search(fullRealmsTree
? RealmsUtils.buildBaseQuery()
- : RealmsUtils.buildKeywordQuery(input)).getResult())
+ : RealmsUtils.buildNameQuery(input)).getResult())
: List.<RealmTO>of()).stream().
map(RealmTO::getFullPath).iterator();
}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java
index c578e59425..6a4375fd94 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/RealmsUtils.java
@@ -38,8 +38,8 @@ public final class RealmsUtils {
return StringUtils.isNotBlank(input) && !"*".equals(input);
}
- public static RealmQuery buildKeywordQuery(final String input) {
- return new RealmQuery.Builder().keyword(input.contains("*") ? input : "*" + input + "*").build();
+ public static RealmQuery buildNameQuery(final String input) {
+ return new RealmQuery.Builder().fiql("name=~" + (input.contains("*") ? input : "*" + input + "*")).build();
}
public static RealmQuery buildBaseQuery() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java
index 9cfeb117fb..f0c88378dc 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RealmChoicePanel.java
@@ -140,7 +140,7 @@ public class RealmChoicePanel extends Panel {
String rootRealmName = StringUtils.substringAfterLast(rootRealm, "/");
List<RealmTO> realmTOs = realmRestClient.search(
- RealmsUtils.buildKeywordQuery(SyncopeConstants.ROOT_REALM.equals(rootRealm)
+ RealmsUtils.buildNameQuery(SyncopeConstants.ROOT_REALM.equals(rootRealm)
? SyncopeConstants.ROOT_REALM : rootRealmName)).getResult();
return realmTOs.stream().
@@ -377,7 +377,7 @@ public class RealmChoicePanel extends Panel {
protected Map<String, Pair<RealmTO, List<RealmTO>>> reloadRealmParentMap() {
List<RealmTO> realmsToList = realmRestClient.search(fullRealmsTree
? RealmsUtils.buildBaseQuery()
- : RealmsUtils.buildKeywordQuery(searchQuery)).getResult();
+ : RealmsUtils.buildNameQuery(searchQuery)).getResult();
return reloadRealmParentMap(realmsToList.stream().
sorted(Comparator.comparing(RealmTO::getName)).
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
index 6f612e41af..259f40aba6 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
@@ -124,7 +124,7 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> extends BaseAjaxWizar
protected List<String> searchRealms(final String realmQuery) {
return realmRestClient.search(fullRealmsTree
? RealmsUtils.buildBaseQuery()
- : RealmsUtils.buildKeywordQuery(realmQuery)).
+ : RealmsUtils.buildNameQuery(realmQuery)).
getResult().stream().map(RealmTO::getFullPath).collect(Collectors.toList());
}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
index 478d1d1fae..665e3f487e 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
@@ -96,7 +96,7 @@ public class Details<T extends AnyTO> extends WizardStep {
? getRealmsFromLinks(realms.getRealmChoicePanel().getLinks())
: (fullRealmsTree
? realmRestClient.search(RealmsUtils.buildBaseQuery())
- : realmRestClient.search(RealmsUtils.buildKeywordQuery(input))).getResult()).
+ : realmRestClient.search(RealmsUtils.buildNameQuery(input))).getResult()).
stream().map(RealmTO::getFullPath).
filter(fullPath -> authRealms.stream().anyMatch(
authRealm -> fullPath.startsWith(authRealm))).iterator();
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java
index d0b1eaf89c..11818c3251 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/role/RoleWizardBuilder.java
@@ -160,7 +160,7 @@ public class RoleWizardBuilder extends BaseAjaxWizardBuilder<RoleWrapper> {
protected Iterator<String> getChoices(final String input) {
return realmRestClient.search(fullRealmsTree
? RealmsUtils.buildBaseQuery()
- : RealmsUtils.buildKeywordQuery(input)).getResult().stream().
+ : RealmsUtils.buildNameQuery(input)).getResult().stream().
map(RealmTO::getFullPath).iterator();
}
};
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
index 1d4b530059..7b64960003 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
+++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClient.java
@@ -46,6 +46,7 @@ import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.search.ConnObjectTOFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
+import org.apache.syncope.common.lib.search.RealmFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.rest.api.Preference;
@@ -76,6 +77,15 @@ public class SyncopeClient {
}
+ /**
+ * Returns a new instance of {@link RealmFiqlSearchConditionBuilder}, for assisted building of FIQL queries.
+ *
+ * @return default instance of {@link RealmFiqlSearchConditionBuilder}
+ */
+ public static RealmFiqlSearchConditionBuilder getRealmFiqlSearchConditionBuilder() {
+ return new RealmFiqlSearchConditionBuilder();
+ }
+
/**
* Returns a new instance of {@link UserFiqlSearchConditionBuilder}, for assisted building of FIQL queries.
*
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmCompleteCondition.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmCompleteCondition.java
new file mode 100644
index 0000000000..49976b2e77
--- /dev/null
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmCompleteCondition.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.search;
+
+public interface RealmCompleteCondition extends SyncopeCompleteCondition<RealmPartialCondition, RealmProperty> {
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmFiqlSearchConditionBuilder.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmFiqlSearchConditionBuilder.java
new file mode 100644
index 0000000000..7ee9d31938
--- /dev/null
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmFiqlSearchConditionBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.search;
+
+import java.util.Map;
+
+public class RealmFiqlSearchConditionBuilder
+ extends AbstractFiqlSearchConditionBuilder<RealmProperty, RealmPartialCondition, RealmCompleteCondition> {
+
+ private static final long serialVersionUID = 324753886224642253L;
+
+ protected static class Builder extends AbstractFiqlSearchConditionBuilder.Builder<
+ RealmProperty, RealmPartialCondition, RealmCompleteCondition>
+ implements RealmProperty, RealmPartialCondition, RealmCompleteCondition {
+
+ public Builder(final Map<String, String> properties) {
+ super(properties);
+ }
+
+ public Builder(final RealmFiqlSearchConditionBuilder.Builder parent) {
+ super(parent);
+ }
+ }
+
+ @Override
+ protected Builder newBuilderInstance() {
+ return new Builder(properties);
+ }
+
+ @Override
+ public RealmProperty is(final String property) {
+ return newBuilderInstance().is(property);
+ }
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmPartialCondition.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmPartialCondition.java
new file mode 100644
index 0000000000..dda92a61d9
--- /dev/null
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmPartialCondition.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.search;
+
+public interface RealmPartialCondition extends SyncopePartialCondition<RealmProperty, RealmCompleteCondition> {
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmProperty.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmProperty.java
new file mode 100644
index 0000000000..1cffb03f28
--- /dev/null
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/RealmProperty.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.search;
+
+public interface RealmProperty extends SyncopeProperty<RealmCompleteCondition> {
+}
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java
index 5d13ea2b8b..893daba106 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/AnyQuery.java
@@ -74,7 +74,7 @@ public class AnyQuery extends AbstractQuery {
+ "primarily meant for containing Users, Groups and Any Objects", schema =
@Schema(implementation = String.class, defaultValue = SyncopeConstants.ROOT_REALM, externalDocs =
@ExternalDocumentation(description = "Apache Syncope Reference Guide",
- url = "https://syncope.apache.org/docs/3.0/reference-guide.html#realms")))
+ url = "https://syncope.apache.org/docs/4.0/reference-guide.html#realms")))
public String getRealm() {
return realm;
}
@@ -120,7 +120,7 @@ public class AnyQuery extends AbstractQuery {
+ "feed.", example = "username==rossini", schema =
@Schema(implementation = String.class, externalDocs =
@ExternalDocumentation(description = "Apache Syncope Reference Guide",
- url = "https://syncope.apache.org/docs/3.0/reference-guide.html#search")))
+ url = "https://syncope.apache.org/docs/4.0/reference-guide.html#search")))
@QueryParam(JAXRSService.PARAM_FIQL)
public void setFiql(final String fiql) {
this.fiql = fiql;
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java
index badd154610..2d23920a84 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/beans/RealmQuery.java
@@ -18,6 +18,9 @@
*/
package org.apache.syncope.common.rest.api.beans;
+import io.swagger.v3.oas.annotations.ExternalDocumentation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.ws.rs.QueryParam;
import java.util.Collection;
import java.util.HashSet;
@@ -27,6 +30,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
public class RealmQuery extends AbstractQuery {
@@ -39,8 +43,8 @@ public class RealmQuery extends AbstractQuery {
return new RealmQuery();
}
- public Builder keyword(final String keyword) {
- getInstance().setKeyword(keyword);
+ public Builder fiql(final String fiql) {
+ getInstance().setFiql(fiql);
return this;
}
@@ -63,17 +67,23 @@ public class RealmQuery extends AbstractQuery {
}
}
- private String keyword;
+ private String fiql;
private Set<String> bases;
- public String getKeyword() {
- return keyword;
+ public String getFiql() {
+ return fiql;
}
- @QueryParam("keyword")
- public void setKeyword(final String keyword) {
- this.keyword = keyword;
+ @Parameter(name = JAXRSService.PARAM_FIQL, description = "Feed Item Query Language (FIQL, pronounced “fickle”) is "
+ + "a simple but flexible, URI-friendly syntax for expressing filters across the entries in a syndicated "
+ + "feed.", example = "name==department1", schema =
+ @Schema(implementation = String.class, externalDocs =
+ @ExternalDocumentation(description = "Apache Syncope Reference Guide",
+ url = "https://syncope.apache.org/docs/4.0/reference-guide.html#search")))
+ @QueryParam(JAXRSService.PARAM_FIQL)
+ public void setFiql(final String fiql) {
+ this.fiql = fiql;
}
public Set<String> getBases() {
@@ -99,7 +109,7 @@ public class RealmQuery extends AbstractQuery {
RealmQuery other = (RealmQuery) obj;
return new EqualsBuilder().
appendSuper(super.equals(obj)).
- append(keyword, other.keyword).
+ append(fiql, other.fiql).
append(bases, other.bases).
build();
}
@@ -108,7 +118,7 @@ public class RealmQuery extends AbstractQuery {
public int hashCode() {
return new HashCodeBuilder().
appendSuper(super.hashCode()).
- append(keyword).
+ append(fiql).
append(bases).
build();
}
diff --git a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
index ac1b977f30..34a7387592 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
+++ b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/ReconciliationLogic.java
@@ -610,7 +610,7 @@ public class ReconciliationLogic extends AbstractLogic<EntityTO> {
orElseThrow(() -> new NotFoundException("Realm " + realm));
Set<String> adminRealms = RealmUtils.getEffective(AuthContextUtils.getAuthorizations().get(entitlement), realm);
- SearchCond effectiveCond = searchCond == null ? anyUtils.dao().getAllMatchingCond() : searchCond;
+ SearchCond effectiveCond = searchCond == null ? anySearchDAO.getAllMatchingCond() : searchCond;
List<Any> matching;
if (spec.getIgnorePaging()) {
diff --git a/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdMRESTCXFContext.java b/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdMRESTCXFContext.java
index bf74c2ee2a..70d773f86b 100644
--- a/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdMRESTCXFContext.java
+++ b/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdMRESTCXFContext.java
@@ -27,7 +27,7 @@ import org.apache.syncope.core.logic.ReconciliationLogic;
import org.apache.syncope.core.logic.RemediationLogic;
import org.apache.syncope.core.logic.ResourceLogic;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.rest.cxf.service.ConnectorServiceImpl;
import org.apache.syncope.core.rest.cxf.service.ReconciliationServiceImpl;
import org.apache.syncope.core.rest.cxf.service.RemediationServiceImpl;
@@ -48,7 +48,7 @@ public class IdMRESTCXFContext {
@ConditionalOnMissingBean
@Bean
public ReconciliationService reconciliationService(
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final ReconciliationLogic reconciliationLogic) {
return new ReconciliationServiceImpl(searchCondVisitor, reconciliationLogic);
diff --git a/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java b/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
index 43480eab2b..7cc4829b40 100644
--- a/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
+++ b/core/idm/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ReconciliationServiceImpl.java
@@ -48,16 +48,18 @@ import org.apache.syncope.common.rest.api.beans.ReconQuery;
import org.apache.syncope.common.rest.api.service.ReconciliationService;
import org.apache.syncope.core.logic.ReconciliationLogic;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.persistence.api.search.FilterVisitor;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.identityconnectors.framework.common.objects.filter.Filter;
-public class ReconciliationServiceImpl extends AbstractSearchService implements ReconciliationService {
+public class ReconciliationServiceImpl
+ extends AbstractSearchService<AnySearchCondVisitor>
+ implements ReconciliationService {
protected final ReconciliationLogic logic;
- public ReconciliationServiceImpl(final SearchCondVisitor searchCondVisitor, final ReconciliationLogic logic) {
+ public ReconciliationServiceImpl(final AnySearchCondVisitor searchCondVisitor, final ReconciliationLogic logic) {
super(searchCondVisitor);
this.logic = logic;
}
@@ -150,7 +152,7 @@ public class ReconciliationServiceImpl extends AbstractSearchService implements
SearchCond searchCond = StringUtils.isBlank(query.getFiql())
? null
- : getSearchCond(query.getFiql(), realm);
+ : getSearchCond(query.getFiql());
StreamingOutput sout = os -> logic.push(
searchCond,
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
index e9b26d2528..cadacdb12d 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
@@ -172,7 +172,7 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupCR, GroupUR> {
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.GROUP_SEARCH), realm);
- SearchCond effectiveCond = searchCond == null ? groupDAO.getAllMatchingCond() : searchCond;
+ SearchCond effectiveCond = searchCond == null ? searchDAO.getAllMatchingCond() : searchCond;
long count = searchDAO.count(base, recursive, authRealms, effectiveCond, AnyTypeKind.GROUP);
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
index ae2cdeaf9c..bf297dac35 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RealmLogic.java
@@ -121,8 +121,8 @@ public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
@PreAuthorize("isAuthenticated()")
@Transactional(readOnly = true)
public Page<RealmTO> search(
- final String keyword,
final Set<String> bases,
+ final SearchCond searchCond,
final Pageable pageable) {
Set<String> baseRealms = new HashSet<>();
@@ -135,14 +135,15 @@ public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
}
}
- long count = realmSearchDAO.countDescendants(baseRealms, keyword);
+ SearchCond effectiveCond = searchCond == null ? realmSearchDAO.getAllMatchingCond() : searchCond;
+
+ long count = realmSearchDAO.count(baseRealms, effectiveCond);
Set<String> authorizations = AuthContextUtils.getAuthorizations().
getOrDefault(IdRepoEntitlement.REALM_SEARCH, Set.of());
- List<RealmTO> result = realmSearchDAO.findDescendants(baseRealms, keyword, pageable).stream().
+ List<RealmTO> result = realmSearchDAO.search(baseRealms, effectiveCond, pageable).stream().
map(realm -> binder.getRealmTO(
- realm, authorizations.stream().
- anyMatch(auth -> realm.getFullPath().startsWith(auth)))).
+ realm, authorizations.stream().anyMatch(auth -> realm.getFullPath().startsWith(auth)))).
sorted(Comparator.comparing(RealmTO::getFullPath)).
toList();
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 83c335bf37..c93a2d8ac1 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
@@ -179,7 +179,7 @@ public class UserLogic extends AbstractAnyLogic<UserTO, UserCR, UserUR> {
Set<String> authRealms = RealmUtils.getEffective(
AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.USER_SEARCH), realm);
- SearchCond effectiveCond = searchCond == null ? userDAO.getAllMatchingCond() : searchCond;
+ SearchCond effectiveCond = searchCond == null ? searchDAO.getAllMatchingCond() : searchCond;
long count = searchDAO.count(base, recursive, authRealms, effectiveCond, AnyTypeKind.USER);
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
index 2615ade450..806ec79c36 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/IdRepoRESTCXFContext.java
@@ -95,7 +95,8 @@ import org.apache.syncope.core.persistence.api.dao.BatchDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.RealmSearchCondVisitor;
import org.apache.syncope.core.rest.cxf.service.AccessTokenServiceImpl;
import org.apache.syncope.core.rest.cxf.service.AnyObjectServiceImpl;
import org.apache.syncope.core.rest.cxf.service.AnyTypeClassServiceImpl;
@@ -324,7 +325,7 @@ public class IdRepoRESTCXFContext {
@ConditionalOnMissingBean
@Bean
public AnyObjectService anyObjectService(final AnyObjectDAO anyObjectDAO, final AnyObjectLogic anyObjectLogic,
- final SearchCondVisitor searchCondVisitor) {
+ final AnySearchCondVisitor searchCondVisitor) {
return new AnyObjectServiceImpl(searchCondVisitor, anyObjectDAO, anyObjectLogic);
}
@@ -367,7 +368,7 @@ public class IdRepoRESTCXFContext {
@ConditionalOnMissingBean
@Bean
public GroupService groupService(final GroupDAO groupDAO, final GroupLogic groupLogic,
- final SearchCondVisitor searchCondVisitor) {
+ final AnySearchCondVisitor searchCondVisitor) {
return new GroupServiceImpl(searchCondVisitor, groupDAO, groupLogic);
}
@@ -397,8 +398,8 @@ public class IdRepoRESTCXFContext {
@ConditionalOnMissingBean
@Bean
- public RealmService realmService(final RealmLogic realmLogic) {
- return new RealmServiceImpl(realmLogic);
+ public RealmService realmService(final RealmLogic realmLogic, final RealmSearchCondVisitor searchCondVisitor) {
+ return new RealmServiceImpl(realmLogic, searchCondVisitor);
}
@ConditionalOnMissingBean
@@ -463,7 +464,7 @@ public class IdRepoRESTCXFContext {
final UserDAO userDAO,
final UserLogic userLogic,
final SyncopeLogic syncopeLogic,
- final SearchCondVisitor searchCondVisitor) {
+ final AnySearchCondVisitor searchCondVisitor) {
return new UserServiceImpl(searchCondVisitor, userDAO, userLogic, syncopeLogic);
}
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java
index 9c1850e23d..3c0fe35c87 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/SyncopeOpenApiCustomizer.java
@@ -92,7 +92,7 @@ public class SyncopeOpenApiCustomizer extends OpenApiCustomizer {
ExternalDocumentation extDoc = new ExternalDocumentation();
extDoc.setDescription("Apache Syncope Reference Guide");
- extDoc.setUrl("https://syncope.apache.org/docs/3.0/reference-guide.html#domains");
+ extDoc.setUrl("https://syncope.apache.org/docs/4.0/reference-guide.html#domains");
Schema<String> schema = new Schema<>();
schema.setDescription("Domains are built to facilitate multitenancy.");
@@ -113,7 +113,7 @@ public class SyncopeOpenApiCustomizer extends OpenApiCustomizer {
ExternalDocumentation extDoc = new ExternalDocumentation();
extDoc.setDescription("Apache Syncope Reference Guide");
- extDoc.setUrl("https://syncope.apache.org/docs/3.0/reference-guide.html#delegation");
+ extDoc.setUrl("https://syncope.apache.org/docs/4.0/reference-guide.html#delegation");
Schema<String> schema = new Schema<>();
schema.setDescription("Acton behalf of someone else");
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
index 5137b272e8..3e98e2e49f 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractAnyService.java
@@ -54,15 +54,16 @@ import org.apache.syncope.core.logic.AbstractAnyLogic;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.springframework.data.domain.Page;
public abstract class AbstractAnyService<TO extends AnyTO, CR extends AnyCR, UR extends AnyUR>
- extends AbstractSearchService implements AnyService<TO> {
+ extends AbstractSearchService<AnySearchCondVisitor>
+ implements AnyService<TO> {
- public AbstractAnyService(final SearchCondVisitor searchCondVisitor) {
+ public AbstractAnyService(final AnySearchCondVisitor searchCondVisitor) {
super(searchCondVisitor);
}
@@ -117,7 +118,7 @@ public abstract class AbstractAnyService<TO extends AnyTO, CR extends AnyCR, UR
String realm = Strings.CS.prependIfMissing(anyQuery.getRealm(), SyncopeConstants.ROOT_REALM);
SearchCond searchCond = StringUtils.isBlank(anyQuery.getFiql())
? null
- : getSearchCond(anyQuery.getFiql(), realm);
+ : getSearchCond(anyQuery.getFiql());
try {
Page<TO> result = getAnyLogic().search(
searchCond,
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractSearchService.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractSearchService.java
index 97afca154d..4e6ce53835 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractSearchService.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractSearchService.java
@@ -24,19 +24,18 @@ import org.apache.cxf.jaxrs.ext.search.SearchCondition;
import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.SyncopeAbstractSearchCondVisitor;
-public abstract class AbstractSearchService extends AbstractService {
+public abstract class AbstractSearchService<V extends SyncopeAbstractSearchCondVisitor> extends AbstractService {
- protected final SearchCondVisitor searchCondVisitor;
+ protected final V searchCondVisitor;
- public AbstractSearchService(final SearchCondVisitor searchCondVisitor) {
+ public AbstractSearchService(final V searchCondVisitor) {
this.searchCondVisitor = searchCondVisitor;
}
- protected SearchCond getSearchCond(final String fiql, final String realm) {
+ protected SearchCond getSearchCond(final String fiql) {
try {
- searchCondVisitor.setRealm(realm);
SearchCondition<SearchBean> sc = searchContext.getCondition(fiql, SearchBean.class);
sc.accept(searchCondVisitor);
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
index 8a6adf8278..cb3e923470 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceImpl.java
@@ -34,7 +34,7 @@ import org.apache.syncope.core.logic.AbstractAnyLogic;
import org.apache.syncope.core.logic.AnyObjectLogic;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
public class AnyObjectServiceImpl extends AbstractAnyService<AnyObjectTO, AnyObjectCR, AnyObjectUR>
implements AnyObjectService {
@@ -44,7 +44,7 @@ public class AnyObjectServiceImpl extends AbstractAnyService<AnyObjectTO, AnyObj
protected final AnyObjectLogic logic;
public AnyObjectServiceImpl(
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final AnyObjectDAO anyObjectDAO,
final AnyObjectLogic logic) {
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
index ecac8f0f82..5faa4f5472 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
@@ -31,7 +31,7 @@ import org.apache.syncope.core.logic.AbstractAnyLogic;
import org.apache.syncope.core.logic.GroupLogic;
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.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
public class GroupServiceImpl extends AbstractAnyService<GroupTO, GroupCR, GroupUR> implements GroupService {
@@ -40,7 +40,7 @@ public class GroupServiceImpl extends AbstractAnyService<GroupTO, GroupCR, Group
protected final GroupLogic logic;
public GroupServiceImpl(
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final GroupDAO groupDAO,
final GroupLogic logic) {
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java
index 866ca7bc24..7d393d70c4 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RealmServiceImpl.java
@@ -20,33 +20,47 @@ package org.apache.syncope.core.rest.cxf.service;
import jakarta.ws.rs.core.Response;
import java.net.URI;
-import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.to.PagedResult;
import org.apache.syncope.common.lib.to.ProvisioningResult;
import org.apache.syncope.common.lib.to.RealmTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.rest.api.RESTHeaders;
import org.apache.syncope.common.rest.api.beans.RealmQuery;
import org.apache.syncope.common.rest.api.service.RealmService;
import org.apache.syncope.core.logic.RealmLogic;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.search.RealmSearchCondVisitor;
import org.springframework.data.domain.Page;
-public class RealmServiceImpl extends AbstractService implements RealmService {
+public class RealmServiceImpl extends AbstractSearchService<RealmSearchCondVisitor> implements RealmService {
protected final RealmLogic logic;
- public RealmServiceImpl(final RealmLogic logic) {
+ public RealmServiceImpl(final RealmLogic logic, final RealmSearchCondVisitor searchCondVisitor) {
+ super(searchCondVisitor);
this.logic = logic;
}
@Override
public PagedResult<RealmTO> search(final RealmQuery query) {
- Page<RealmTO> result = logic.search(
- Optional.ofNullable(query.getKeyword()).map(k -> k.replace('*', '%')).orElse(null),
- query.getBases(),
- pageable(query));
- return buildPagedResult(result);
+ SearchCond searchCond = StringUtils.isBlank(query.getFiql()) ? null : getSearchCond(query.getFiql());
+ try {
+ Page<RealmTO> result = logic.search(
+ query.getBases(),
+ searchCond,
+ pageable(query));
+ return buildPagedResult(result);
+ } catch (IllegalArgumentException e) {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchParameters);
+ sce.getElements().add(query.getFiql());
+ sce.getElements().add(ExceptionUtils.getRootCauseMessage(e));
+ throw sce;
+ }
}
@Override
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
index 1d34a8ab15..12a89c52b6 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/UserServiceImpl.java
@@ -33,7 +33,7 @@ import org.apache.syncope.core.logic.SyncopeLogic;
import org.apache.syncope.core.logic.UserLogic;
import org.apache.syncope.core.persistence.api.dao.AnyDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
public class UserServiceImpl extends AbstractAnyService<UserTO, UserCR, UserUR> implements UserService {
@@ -44,7 +44,7 @@ public class UserServiceImpl extends AbstractAnyService<UserTO, UserCR, UserUR>
protected final SyncopeLogic syncopeLogic;
public UserServiceImpl(
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final UserDAO userDAO,
final UserLogic logic,
final SyncopeLogic syncopeLogic) {
diff --git a/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java b/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java
index eebbc66863..0714b29869 100644
--- a/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java
+++ b/core/idrepo/rest-cxf/src/test/java/org/apache/syncope/core/rest/cxf/service/AnyObjectServiceTest.java
@@ -66,7 +66,7 @@ import org.apache.syncope.common.rest.api.service.AnyObjectService;
import org.apache.syncope.core.logic.AnyObjectLogic;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.persistence.api.search.SyncopePage;
import org.apache.syncope.core.rest.cxf.AddETagFilter;
import org.apache.syncope.core.rest.cxf.RestServiceExceptionMapper;
@@ -145,7 +145,7 @@ public class AnyObjectServiceTest {
return result;
});
- SearchCondVisitor searchCondVisitor = mock(SearchCondVisitor.class);
+ AnySearchCondVisitor searchCondVisitor = mock(AnySearchCondVisitor.class);
when(searchCondVisitor.getQuery()).thenReturn(new SearchCond());
@SuppressWarnings("unchecked")
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java
index 9049114eed..053c342951 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnyDAO.java
@@ -42,19 +42,6 @@ public interface AnyDAO<A extends Any> extends DAO<A> {
A authFind(String key);
- /**
- * Find any objects by derived attribute value. This method could fail if one or more string literals contained
- * into the derived attribute value provided derive from identifier (schema key) replacement. When you are going to
- * specify a derived attribute expression you must be quite sure that string literals used to build the expression
- * cannot be found into the attribute values used to replace attribute schema keys used as identifiers.
- *
- * @param expression JEXL expression
- * @param value derived attribute value
- * @param ignoreCaseMatch whether comparison for string values should take case into account or not
- * @return list of any objects
- */
- List<A> findByDerAttrValue(String expression, String value, boolean ignoreCaseMatch);
-
List<A> findByResourcesContaining(ExternalResource resource);
Page<? extends A> findAll(Pageable pageable);
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java
index 85dc4faf34..2e0e0b7d91 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/AnySearchDAO.java
@@ -21,6 +21,8 @@ package org.apache.syncope.core.persistence.api.dao;
import java.util.List;
import java.util.Set;
import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.Realm;
@@ -29,12 +31,31 @@ import org.springframework.data.domain.Sort;
public interface AnySearchDAO {
+ /**
+ * Find any objects by derived attribute value. This method could fail if one or more string literals contained
+ * into the derived attribute value provided derive from identifier (schema key) replacement. When you are going to
+ * specify a derived attribute expression you must be quite sure that string literals used to build the expression
+ * cannot be found into the attribute values used to replace attribute schema keys used as identifiers.
+ *
+ * @param expression JEXL expression
+ * @param value derived attribute value
+ * @param ignoreCaseMatch whether comparison for string values should take case into account or not
+ * @param kind any type kind
+ * @param <T> any
+ * @return list of any objects
+ */
+ <T extends Any> List<T> findByDerAttrValue(
+ String expression,
+ String value,
+ boolean ignoreCaseMatch,
+ AnyTypeKind kind);
+
/**
* @param base Realm to start searching from
* @param recursive whether search should recursively include results from child Realms
* @param adminRealms realms for which the caller owns the proper entitlement(s)
* @param searchCondition the search condition
- * @param kind any object
+ * @param kind any type kind
* @return size of search result
*/
long count(
@@ -55,7 +76,7 @@ public interface AnySearchDAO {
/**
* @param searchCondition the search condition
* @param orderBy list of ordering clauses
- * @param kind any object
+ * @param kind any type kind
* @param <T> any
* @return the list of any objects matching the given search condition
*/
@@ -67,7 +88,7 @@ public interface AnySearchDAO {
* @param adminRealms realms for which the caller owns the proper entitlement(s)
* @param searchCondition the search condition
* @param pageable paging information
- * @param kind any object
+ * @param kind any type kind
* @param <T> any
* @return the list of any objects matching the given search condition (in the given page)
*/
@@ -78,4 +99,13 @@ public interface AnySearchDAO {
SearchCond searchCondition,
Pageable pageable,
AnyTypeKind kind);
+
+ /**
+ * @return the search condition to match all entities
+ */
+ default SearchCond getAllMatchingCond() {
+ AnyCond idCond = new AnyCond(AttrCond.Type.ISNOTNULL);
+ idCond.setSchema("id");
+ return SearchCond.of(idCond);
+ }
}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmSearchDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmSearchDAO.java
index 3b3a740b5f..e72e865653 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmSearchDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/RealmSearchDAO.java
@@ -22,6 +22,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.springframework.data.domain.Pageable;
@@ -33,15 +36,7 @@ public interface RealmSearchDAO {
List<Realm> findChildren(Realm realm);
- long countDescendants(String base, String keyword);
-
- long countDescendants(Set<String> bases, String keyword);
-
- List<Realm> findDescendants(String base, String keyword, Pageable pageable);
-
- List<Realm> findDescendants(Set<String> bases, String keyword, Pageable pageable);
-
- List<String> findDescendants(String base, String prefix);
+ List<Realm> findDescendants(String base, String prefix);
default void findAncestors(final List<Realm> result, final Realm realm) {
if (realm.getParent() != null && !result.contains(realm.getParent())) {
@@ -56,4 +51,27 @@ public interface RealmSearchDAO {
findAncestors(result, realm);
return result;
}
+
+ /**
+ * Find realmss by derived attribute value. This method could fail if one or more string literals contained
+ * into the derived attribute value provided derive from identifier (schema key) replacement. When you are going to
+ * specify a derived attribute expression you must be quite sure that string literals used to build the expression
+ * cannot be found into the attribute values used to replace attribute schema keys used as identifiers.
+ *
+ * @param expression JEXL expression
+ * @param value derived attribute value
+ * @param ignoreCaseMatch whether comparison for string values should take case into account or not
+ * @return list of realms
+ */
+ List<Realm> findByDerAttrValue(String expression, String value, boolean ignoreCaseMatch);
+
+ long count(Set<String> bases, SearchCond cond);
+
+ List<Realm> search(Set<String> bases, SearchCond cond, Pageable pageable);
+
+ default SearchCond getAllMatchingCond() {
+ AnyCond idCond = new AnyCond(AttrCond.Type.ISNOTNULL);
+ idCond.setSchema("id");
+ return SearchCond.of(idCond);
+ }
}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/AnySearchCondVisitor.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/AnySearchCondVisitor.java
new file mode 100644
index 0000000000..d31f7f5995
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/AnySearchCondVisitor.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.search;
+
+import java.util.Optional;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+
+public class AnySearchCondVisitor extends SyncopeAbstractSearchCondVisitor {
+
+
+ @Override
+ protected Optional<SpecialAttr> getSpecialAttrName(final String propertyName) {
+ return SpecialAttr.fromString(propertyName);
+ }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/RealmSearchCondVisitor.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/RealmSearchCondVisitor.java
new file mode 100644
index 0000000000..b31cd96c20
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/RealmSearchCondVisitor.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.search;
+
+import java.util.Optional;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+
+public class RealmSearchCondVisitor extends SyncopeAbstractSearchCondVisitor {
+
+
+ @Override
+ protected Optional<SpecialAttr> getSpecialAttrName(final String propertyName) {
+ return Optional.empty();
+ }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondConverter.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondConverter.java
index 83c1486f12..3a8ada5572 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondConverter.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondConverter.java
@@ -37,19 +37,16 @@ public final class SearchCondConverter {
/**
* Parses a FIQL expression into Syncope's {@link SearchCond}, using {@link SyncopeFiqlParser}.
*
+ * @param <V> concrete {@link SyncopeAbstractSearchCondVisitor} implementation
* @param visitor visitor instance
* @param fiql FIQL string
- * @param realms optional realm to provide to {@link SearchCondVisitor}
* @return {@link SearchCond} instance for given FIQL expression
*/
- public static SearchCond convert(final SearchCondVisitor visitor, final String fiql, final String... realms) {
+ public static <V extends SyncopeAbstractSearchCondVisitor> SearchCond convert(final V visitor, final String fiql) {
SyncopeFiqlParser<SearchBean> parser = new SyncopeFiqlParser<>(
SearchBean.class, AbstractFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
try {
- if (realms != null && realms.length > 0) {
- visitor.setRealm(realms[0]);
- }
SearchCondition<SearchBean> sc = parser.parse(URLDecoder.decode(fiql, StandardCharsets.UTF_8));
sc.accept(visitor);
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SyncopeAbstractSearchCondVisitor.java
similarity index 96%
rename from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
rename to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SyncopeAbstractSearchCondVisitor.java
index 2f1a5adfae..dd27c91d03 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SearchCondVisitor.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/search/SyncopeAbstractSearchCondVisitor.java
@@ -47,20 +47,11 @@ import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
/**
* Visits CXF's {@link SearchBean} and produces {@link SearchCond}.
*/
-public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean, SearchCond> {
-
- protected static final ThreadLocal<String> REALM = new ThreadLocal<>();
+public abstract class SyncopeAbstractSearchCondVisitor
+ extends AbstractSearchConditionVisitor<SearchBean, SearchCond> {
protected static final ThreadLocal<SearchCond> SEARCH_COND = new ThreadLocal<>();
- public SearchCondVisitor() {
- super(null);
- }
-
- public void setRealm(final String realm) {
- REALM.set(realm);
- }
-
protected static AttrCond createAttrCond(final String schema) {
AttrCond attrCond = SearchableFields.contains(schema)
? new AnyCond()
@@ -83,6 +74,7 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
ConditionType ct = sc.getConditionType();
if (sc instanceof final SyncopeFiqlSearchCondition<SearchBean> sfsc
&& sc.getConditionType() == ConditionType.CUSTOM) {
+
switch (sfsc.getOperator()) {
case SyncopeFiqlParser.IEQ:
ct = ConditionType.EQUALS;
@@ -101,10 +93,16 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
return ct;
}
+ protected SyncopeAbstractSearchCondVisitor() {
+ super(null);
+ }
+
+ protected abstract Optional<SpecialAttr> getSpecialAttrName(String propertyName);
+
@SuppressWarnings("ConvertToStringSwitch")
protected SearchCond visitPrimitive(final SearchCondition<SearchBean> sc) {
String name = getRealPropertyName(sc.getStatement().getProperty());
- Optional<SpecialAttr> specialAttrName = SpecialAttr.fromString(name);
+ Optional<SpecialAttr> specialAttrName = getSpecialAttrName(name);
String value = getValue(sc);
Optional<SpecialAttr> specialAttrValue = SpecialAttr.fromString(value);
diff --git a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
index 34dc473efc..72ce4b59b3 100644
--- a/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
+++ b/core/persistence-api/src/test/java/org/apache/syncope/core/persistence/api/search/SearchCondConverterTest.java
@@ -39,7 +39,7 @@ import org.junit.jupiter.api.Test;
public class SearchCondConverterTest {
- private static final SearchCondVisitor VISITOR = new SearchCondVisitor();
+ private static final AnySearchCondVisitor VISITOR = new AnySearchCondVisitor();
@Test
public void eq() {
diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java
index 18e524347a..14de720545 100644
--- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java
+++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/CommonPersistenceContext.java
@@ -23,7 +23,6 @@ import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
import org.apache.syncope.common.lib.types.AnyTypeKind;
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;
import org.apache.syncope.core.persistence.api.dao.RealmChecker;
@@ -33,11 +32,11 @@ import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.am.ClientAppUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtilsFactory;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.RealmSearchCondVisitor;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.persistence.common.attrvalue.DefaultPlainAttrValidationManager;
import org.apache.syncope.core.persistence.common.content.KeymasterConfParamLoader;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.common.entity.DefaultAnyUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -52,8 +51,14 @@ public class CommonPersistenceContext {
@ConditionalOnMissingBean
@Bean
- public SearchCondVisitor searchCondVisitor() {
- return new SearchCondVisitor();
+ public AnySearchCondVisitor anySearchCondVisitor() {
+ return new AnySearchCondVisitor();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ public RealmSearchCondVisitor realmSearchCondVisitor() {
+ return new RealmSearchCondVisitor();
}
@Bean
@@ -158,12 +163,6 @@ public class CommonPersistenceContext {
return new KeymasterConfParamLoader(confParamOps, ctx);
}
- @ConditionalOnMissingBean
- @Bean
- public AnyFinder anyFinder(final @Lazy PlainSchemaDAO plainSchemaDAO, final @Lazy AnySearchDAO anySearchDAO) {
- return new AnyFinder(plainSchemaDAO, anySearchDAO);
- }
-
@ConditionalOnMissingBean
@Bean
public RealmChecker realmChecker(final @Lazy PlainSchemaDAO plainSchemaDAO) {
diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnySearchDAO.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnySearchDAO.java
index da73786c60..106ee3bc6b 100644
--- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnySearchDAO.java
+++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractAnySearchDAO.java
@@ -18,22 +18,14 @@
*/
package org.apache.syncope.core.persistence.common.dao;
-import jakarta.validation.ValidationException;
-import jakarta.validation.constraints.Max;
-import jakarta.validation.constraints.Min;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.AttrSchemaType;
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;
@@ -48,11 +40,8 @@ import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
import org.apache.syncope.core.persistence.api.dao.search.RelationshipCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Any;
-import org.apache.syncope.core.persistence.api.entity.AnyUtils;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-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.Realm;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.slf4j.Logger;
@@ -60,23 +49,17 @@ import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
-public abstract class AbstractAnySearchDAO implements AnySearchDAO {
-
- protected record CheckResult<C extends AttrCond>(PlainSchema schema, PlainAttrValue value, C cond) {
-
- }
+public abstract class AbstractAnySearchDAO extends AbstractSearchDAO implements AnySearchDAO {
protected static final Logger LOG = LoggerFactory.getLogger(AnySearchDAO.class);
- protected static final String ALWAYS_FALSE_CLAUSE = "1=2";
-
- private static final String[] ORDER_BY_NOT_ALLOWED = {
- "serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime"
- };
+ private static final Set<String> ORDER_BY_NOT_ALLOWED = Set.of(
+ "serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime");
- protected static final String[] RELATIONSHIP_FIELDS = { "realm", "uManager", "gManager" };
+ protected static final Set<String> RELATIONSHIP_FIELDS = Set.of("realm", "uManager", "gManager");
protected static SearchCond buildEffectiveCond(
final SearchCond cond,
@@ -106,52 +89,12 @@ public abstract class AbstractAnySearchDAO implements AnySearchDAO {
return SearchCond.and(result);
}
- public static String key(final AttrSchemaType schemaType) {
- String key;
- switch (schemaType) {
- case Boolean:
- key = "booleanValue";
- break;
-
- case Date:
- key = "dateValue";
- break;
-
- case Double:
- key = "doubleValue";
- break;
-
- case Long:
- key = "longValue";
- break;
-
- case Binary:
- key = "binaryValue";
- break;
-
- default:
- key = "stringValue";
- }
-
- return key;
- }
-
protected final RealmSearchDAO realmSearchDAO;
- protected final UserDAO userDAO;
-
- protected final GroupDAO groupDAO;
-
protected final AnyObjectDAO anyObjectDAO;
- protected final PlainSchemaDAO plainSchemaDAO;
-
- protected final EntityFactory entityFactory;
-
protected final AnyUtilsFactory anyUtilsFactory;
- protected final PlainAttrValidationManager validator;
-
public AbstractAnySearchDAO(
final RealmSearchDAO realmSearchDAO,
final UserDAO userDAO,
@@ -162,14 +105,25 @@ public abstract class AbstractAnySearchDAO implements AnySearchDAO {
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator) {
+ super(plainSchemaDAO, userDAO, groupDAO, entityFactory, validator);
this.realmSearchDAO = realmSearchDAO;
- this.userDAO = userDAO;
- this.groupDAO = groupDAO;
this.anyObjectDAO = anyObjectDAO;
- this.plainSchemaDAO = plainSchemaDAO;
- this.entityFactory = entityFactory;
this.anyUtilsFactory = anyUtilsFactory;
- this.validator = validator;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public <A extends Any> List<A> findByDerAttrValue(
+ final String expression,
+ final String value,
+ final boolean ignoreCaseMatch,
+ final AnyTypeKind anyTypeKind) {
+
+ List<SearchCond> conditions = buildDerAttrValueConditions(expression, value, ignoreCaseMatch);
+
+ LOG.debug("Generated search {} conditions: {}", anyTypeKind, conditions);
+
+ return conditions.isEmpty() ? List.of() : search(SearchCond.and(conditions), anyTypeKind);
}
protected abstract long doCount(
@@ -223,109 +177,6 @@ public abstract class AbstractAnySearchDAO implements AnySearchDAO {
Pageable pageable,
AnyTypeKind kind);
- protected CheckResult<AttrCond> check(final AttrCond cond) {
- PlainSchema schema = plainSchemaDAO.findById(cond.getSchema()).
- orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema()));
-
- PlainAttrValue attrValue = new PlainAttrValue();
-
- if (AttrSchemaType.Encrypted == schema.getType()) {
- throw new IllegalArgumentException("Cannot search by encrypted schema " + cond.getSchema());
- }
-
- try {
- if (cond.getType() != AttrCond.Type.LIKE
- && cond.getType() != AttrCond.Type.ILIKE
- && cond.getType() != AttrCond.Type.ISNULL
- && cond.getType() != AttrCond.Type.ISNOTNULL) {
-
- validator.validate(schema, cond.getExpression(), attrValue);
- }
- } catch (ValidationException e) {
- throw new IllegalArgumentException("Could not validate expression " + cond.getExpression());
- }
-
- return new CheckResult<>(schema, attrValue, cond);
- }
-
- protected CheckResult<AnyCond> check(final AnyCond cond, final AnyTypeKind kind) {
- AnyCond computed = new AnyCond(cond.getType());
- computed.setSchema(cond.getSchema());
- computed.setExpression(cond.getExpression());
-
- AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
-
- Field anyField = anyUtils.getField(computed.getSchema()).
- orElseThrow(() -> new IllegalArgumentException("Invalid schema " + computed.getSchema()));
-
- // Keeps track of difference between entity's getKey() and JPA @Id fields
- if ("key".equals(computed.getSchema())) {
- computed.setSchema("id");
- }
-
- PlainSchema schema = entityFactory.newEntity(PlainSchema.class);
- schema.setKey(anyField.getName());
- for (AttrSchemaType attrSchemaType : AttrSchemaType.values()) {
- if (anyField.getType().isAssignableFrom(attrSchemaType.getType())) {
- schema.setType(attrSchemaType);
- }
- }
- if (schema.getType() == null || schema.getType() == AttrSchemaType.Dropdown) {
- schema.setType(AttrSchemaType.String);
- }
-
- // Deal with any Integer fields logically mapping to boolean values
- boolean foundBooleanMin = false;
- boolean foundBooleanMax = false;
- if (Integer.class.equals(anyField.getType())) {
- for (Annotation annotation : anyField.getAnnotations()) {
- if (Min.class.equals(annotation.annotationType())) {
- foundBooleanMin = ((Min) annotation).value() == 0;
- } else if (Max.class.equals(annotation.annotationType())) {
- foundBooleanMax = ((Max) annotation).value() == 1;
- }
- }
- }
- if (foundBooleanMin && foundBooleanMax) {
- schema.setType(AttrSchemaType.Boolean);
- }
-
- // Deal with any fields representing relationships to other entities
- if (ArrayUtils.contains(RELATIONSHIP_FIELDS, computed.getSchema())) {
- computed.setSchema(computed.getSchema() + "_id");
- schema.setType(AttrSchemaType.String);
-
- if (!SyncopeConstants.UUID_PATTERN.matcher(computed.getExpression()).matches()) {
- switch (StringUtils.substringBefore(computed.getSchema(), "_id")) {
- case "uManager" ->
- userDAO.findKey(computed.getExpression()).ifPresent(computed::setExpression);
-
- case "gManager" ->
- groupDAO.findKey(computed.getExpression()).ifPresent(computed::setExpression);
-
- default -> {
- }
- }
- }
- }
-
- PlainAttrValue attrValue = new PlainAttrValue();
- if (computed.getType() != AttrCond.Type.LIKE
- && computed.getType() != AttrCond.Type.ILIKE
- && computed.getType() != AttrCond.Type.ISNULL
- && computed.getType() != AttrCond.Type.ISNOTNULL) {
-
- try {
- validator.validate(schema, computed.getExpression(), attrValue);
- } catch (ValidationException e) {
- LOG.error("Could not validate expression {}", computed.getExpression(), e);
- throw new IllegalArgumentException("Could not validate expression " + computed.getExpression());
- }
- }
-
- return new CheckResult<>(schema, attrValue, computed);
- }
-
protected boolean isPatternMatch(final String clause) {
return clause.indexOf('%') != -1;
}
@@ -417,7 +268,7 @@ public abstract class AbstractAnySearchDAO implements AnySearchDAO {
new Sort.Order(Sort.Direction.ASC, kind == AnyTypeKind.USER ? "username" : "name"));
} else {
effectiveOrderBy = pageable.getSort().stream().
- filter(clause -> !ArrayUtils.contains(ORDER_BY_NOT_ALLOWED, clause.getProperty())).
+ filter(clause -> !ORDER_BY_NOT_ALLOWED.contains(clause.getProperty())).
toList();
}
diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractRealmSearchDAO.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractRealmSearchDAO.java
new file mode 100644
index 0000000000..10fdb3e243
--- /dev/null
+++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractRealmSearchDAO.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.common.dao;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
+import org.apache.syncope.core.persistence.api.dao.UserDAO;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.transaction.annotation.Transactional;
+
+public abstract class AbstractRealmSearchDAO extends AbstractSearchDAO implements RealmSearchDAO {
+
+ private static final Set<String> ORDER_BY_NOT_ALLOWED = Set.of(
+ "serialVersionUID", "parent");
+
+ protected static final Set<String> RELATIONSHIP_FIELDS = Set.of(
+ "parent", "passwordPolicy", "accountPolicy", "authPolicy", "accessPolicy", "attrReleasePolicy",
+ "ticketExpirationPolicy");
+
+ public AbstractRealmSearchDAO(
+ final PlainSchemaDAO plainSchemaDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator) {
+
+ super(plainSchemaDAO, userDAO, groupDAO, entityFactory, validator);
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<Realm> findByDerAttrValue(
+ final String expression,
+ final String value,
+ final boolean ignoreCaseMatch) {
+
+ List<SearchCond> conditions = buildDerAttrValueConditions(expression, value, ignoreCaseMatch);
+
+ LOG.debug("Generated search Realm conditions: {}", conditions);
+
+ return conditions.isEmpty() ? List.of() : search(
+ Set.of(SyncopeConstants.ROOT_REALM), SearchCond.and(conditions), Pageable.unpaged());
+ }
+
+ protected abstract long doCount(Set<String> bases, SearchCond cond);
+
+ @Override
+ public long count(final Set<String> bases, final SearchCond cond) {
+ LOG.debug("Search condition:\n{}", cond);
+ if (cond == null || !cond.isValid()) {
+ LOG.error("Invalid search condition:\n{}", cond);
+ return 0;
+ }
+
+ return doCount(bases, cond);
+ }
+
+ protected abstract List<Realm> doSearch(Set<String> bases, SearchCond cond, Pageable pageable);
+
+ @Override
+ public List<Realm> search(final Set<String> bases, final SearchCond cond, final Pageable pageable) {
+ LOG.debug("Search condition:\n{}", cond);
+ if (cond == null || !cond.isValid()) {
+ LOG.error("Invalid search condition:\n{}", cond);
+ return List.of();
+ }
+
+ List<Sort.Order> effectiveOrderBy;
+ if (pageable.getSort().isEmpty()) {
+ effectiveOrderBy = List.of(new Sort.Order(Sort.Direction.ASC, "name"));
+ } else {
+ effectiveOrderBy = pageable.getSort().stream().
+ filter(clause -> !ORDER_BY_NOT_ALLOWED.contains(clause.getProperty())).
+ toList();
+ }
+
+ return doSearch(
+ bases,
+ cond,
+ pageable.isUnpaged()
+ ? Pageable.unpaged(Sort.by(effectiveOrderBy))
+ : PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(effectiveOrderBy)));
+ }
+}
diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractSearchDAO.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractSearchDAO.java
new file mode 100644
index 0000000000..03c8dad854
--- /dev/null
+++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AbstractSearchDAO.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.common.dao;
+
+import jakarta.validation.ValidationException;
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import org.apache.commons.jexl3.parser.Parser;
+import org.apache.commons.jexl3.parser.ParserConstants;
+import org.apache.commons.jexl3.parser.Token;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+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.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractSearchDAO {
+
+ public record CheckResult<C extends AttrCond>(PlainSchema schema, PlainAttrValue value, C cond) {
+
+ }
+
+ protected static final Logger LOG = LoggerFactory.getLogger(AbstractSearchDAO.class);
+
+ protected static final String ALWAYS_FALSE_CLAUSE = "1=2";
+
+ public static String key(final AttrSchemaType schemaType) {
+ return switch (schemaType) {
+ case Boolean ->
+ "booleanValue";
+
+ case Date ->
+ "dateValue";
+
+ case Double ->
+ "doubleValue";
+
+ case Long ->
+ "longValue";
+
+ case Binary ->
+ "binaryValue";
+
+ default ->
+ "stringValue";
+ };
+ }
+
+ protected static final Comparator<String> LITERAL_COMPARATOR = (l1, l2) -> {
+ if (l1 == null && l2 == null) {
+ return 0;
+ } else if (l1 != null && l2 == null) {
+ return -1;
+ } else if (l1 == null) {
+ return 1;
+ } else if (l1.length() == l2.length()) {
+ return 0;
+ } else if (l1.length() > l2.length()) {
+ return -1;
+ } else {
+ return 1;
+ }
+ };
+
+ /**
+ * Split an attribute value recurring on provided literals/tokens.
+ *
+ * @param attrValue value to be split
+ * @param literals literals/tokens
+ * @return split value
+ */
+ protected static List<String> split(final String attrValue, final List<String> literals) {
+ final List<String> attrValues = new ArrayList<>();
+
+ if (literals.isEmpty()) {
+ attrValues.add(attrValue);
+ } else {
+ for (String token : attrValue.split(Pattern.quote(literals.getFirst()))) {
+ if (!token.isEmpty()) {
+ attrValues.addAll(split(token, literals.subList(1, literals.size())));
+ }
+ }
+ }
+
+ return attrValues;
+ }
+
+ protected static Supplier<SyncopeClientException> syncopeClientException(final String message) {
+ return () -> {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchParameters);
+ sce.getElements().add(message);
+ return sce;
+ };
+ }
+
+ protected final PlainSchemaDAO plainSchemaDAO;
+
+ protected final UserDAO userDAO;
+
+ protected final GroupDAO groupDAO;
+
+ protected final EntityFactory entityFactory;
+
+ protected final PlainAttrValidationManager validator;
+
+ protected AbstractSearchDAO(
+ final PlainSchemaDAO plainSchemaDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator) {
+
+ this.plainSchemaDAO = plainSchemaDAO;
+ this.userDAO = userDAO;
+ this.groupDAO = groupDAO;
+ this.entityFactory = entityFactory;
+ this.validator = validator;
+ }
+
+ protected List<SearchCond> buildDerAttrValueConditions(
+ final String expression,
+ final String value,
+ final boolean ignoreCaseMatch) {
+
+ Parser parser = new Parser(expression);
+
+ // Schema keys
+ List<String> identifiers = new ArrayList<>();
+
+ // Literals
+ List<String> literals = new ArrayList<>();
+
+ // Get schema keys and literals
+ for (Token token = parser.getNextToken(); token != null && StringUtils.isNotBlank(token.toString());
+ token = parser.getNextToken()) {
+
+ if (token.kind == ParserConstants.STRING_LITERAL) {
+ literals.add(token.toString().substring(1, token.toString().length() - 1));
+ }
+
+ if (token.kind == ParserConstants.IDENTIFIER) {
+ identifiers.add(token.toString());
+ }
+ }
+
+ // Sort literals in order to process later literals included into others
+ literals.sort(LITERAL_COMPARATOR);
+
+ // Split value on provided literals
+ List<String> attrValues = split(value, literals);
+
+ if (attrValues.size() != identifiers.size()) {
+ LOG.error("Ambiguous JEXL expression resolution: literals and values have different size");
+ return List.of();
+ }
+
+ List<SearchCond> conditions = new ArrayList<>();
+
+ // Contains used identifiers in order to avoid replications
+ Set<String> used = new HashSet<>();
+
+ // Create several clauses: one for eanch identifiers
+ for (int i = 0; i < identifiers.size() && !used.contains(identifiers.get(i)); i++) {
+ used.add(identifiers.get(i));
+
+ AttrCond cond = plainSchemaDAO.findById(identifiers.get(i)).
+ map(schema -> new AttrCond()).
+ orElseGet(() -> new AnyCond());
+ cond.setType(ignoreCaseMatch ? AttrCond.Type.IEQ : AttrCond.Type.EQ);
+ cond.setSchema(identifiers.get(i));
+ cond.setExpression(attrValues.get(i));
+ conditions.add(SearchCond.of(cond));
+ }
+
+ return conditions;
+ }
+
+ protected CheckResult<AttrCond> check(final AttrCond cond) {
+ PlainSchema schema = plainSchemaDAO.findById(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema()));
+
+ PlainAttrValue attrValue = new PlainAttrValue();
+
+ if (AttrSchemaType.Encrypted == schema.getType()) {
+ throw new IllegalArgumentException("Cannot search by encrypted schema " + cond.getSchema());
+ }
+
+ try {
+ if (cond.getType() != AttrCond.Type.LIKE
+ && cond.getType() != AttrCond.Type.ILIKE
+ && cond.getType() != AttrCond.Type.ISNULL
+ && cond.getType() != AttrCond.Type.ISNOTNULL) {
+
+ validator.validate(schema, cond.getExpression(), attrValue);
+ }
+ } catch (ValidationException e) {
+ throw new IllegalArgumentException("Could not validate expression " + cond.getExpression());
+ }
+
+ return new CheckResult<>(schema, attrValue, cond);
+ }
+
+ protected CheckResult<AnyCond> check(final AnyCond cond, final Field field, final Set<String> relationshipsFields) {
+ AnyCond computed = new AnyCond(cond.getType());
+ computed.setSchema(cond.getSchema());
+ computed.setExpression(cond.getExpression());
+
+ // Keeps track of difference between entity's getKey() and @Id fields
+ if ("key".equals(computed.getSchema())) {
+ computed.setSchema("id");
+ }
+
+ PlainSchema schema = entityFactory.newEntity(PlainSchema.class);
+ schema.setKey(field.getName());
+ for (AttrSchemaType attrSchemaType : AttrSchemaType.values()) {
+ if (field.getType().isAssignableFrom(attrSchemaType.getType())) {
+ schema.setType(attrSchemaType);
+ }
+ }
+ if (schema.getType() == null || schema.getType() == AttrSchemaType.Dropdown) {
+ schema.setType(AttrSchemaType.String);
+ }
+
+ // Deal with any Integer fields logically mapping to boolean values
+ boolean foundBooleanMin = false;
+ boolean foundBooleanMax = false;
+ if (Integer.class.equals(field.getType())) {
+ for (Annotation annotation : field.getAnnotations()) {
+ if (Min.class.equals(annotation.annotationType())) {
+ foundBooleanMin = ((Min) annotation).value() == 0;
+ } else if (Max.class.equals(annotation.annotationType())) {
+ foundBooleanMax = ((Max) annotation).value() == 1;
+ }
+ }
+ }
+ if (foundBooleanMin && foundBooleanMax) {
+ schema.setType(AttrSchemaType.Boolean);
+ }
+
+ // Deal with fields representing relationships to other entities
+ if (relationshipsFields.contains(computed.getSchema())) {
+ computed.setSchema(computed.getSchema() + "_id");
+ schema.setType(AttrSchemaType.String);
+
+ if (!SyncopeConstants.UUID_PATTERN.matcher(computed.getExpression()).matches()) {
+ switch (StringUtils.substringBefore(computed.getSchema(), "_id")) {
+ case "uManager" ->
+ userDAO.findKey(computed.getExpression()).ifPresent(computed::setExpression);
+
+ case "gManager" ->
+ groupDAO.findKey(computed.getExpression()).ifPresent(computed::setExpression);
+
+ default -> {
+ }
+ }
+ }
+ }
+
+ PlainAttrValue attrValue = new PlainAttrValue();
+ if (computed.getType() != AttrCond.Type.LIKE
+ && computed.getType() != AttrCond.Type.ILIKE
+ && computed.getType() != AttrCond.Type.ISNULL
+ && computed.getType() != AttrCond.Type.ISNOTNULL) {
+
+ try {
+ validator.validate(schema, computed.getExpression(), attrValue);
+ } catch (ValidationException e) {
+ LOG.error("Could not validate expression {}", computed.getExpression(), e);
+ throw new IllegalArgumentException("Could not validate expression " + computed.getExpression());
+ }
+ }
+
+ return new CheckResult<>(schema, attrValue, computed);
+ }
+}
diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AnyFinder.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AnyFinder.java
deleted file mode 100644
index 7297bd7f27..0000000000
--- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/dao/AnyFinder.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.core.persistence.common.dao;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Pattern;
-import org.apache.commons.jexl3.parser.Parser;
-import org.apache.commons.jexl3.parser.ParserConstants;
-import org.apache.commons.jexl3.parser.Token;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
-import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
-import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
-import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
-import org.apache.syncope.core.persistence.api.entity.Any;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.transaction.annotation.Transactional;
-
-public class AnyFinder {
-
- protected static final Logger LOG = LoggerFactory.getLogger(AnyFinder.class);
-
- protected static final Comparator<String> LITERAL_COMPARATOR = (l1, l2) -> {
- if (l1 == null && l2 == null) {
- return 0;
- } else if (l1 != null && l2 == null) {
- return -1;
- } else if (l1 == null) {
- return 1;
- } else if (l1.length() == l2.length()) {
- return 0;
- } else if (l1.length() > l2.length()) {
- return -1;
- } else {
- return 1;
- }
- };
-
- /**
- * Split an attribute value recurring on provided literals/tokens.
- *
- * @param attrValue value to be split
- * @param literals literals/tokens
- * @return split value
- */
- protected static List<String> split(final String attrValue, final List<String> literals) {
- final List<String> attrValues = new ArrayList<>();
-
- if (literals.isEmpty()) {
- attrValues.add(attrValue);
- } else {
- for (String token : attrValue.split(Pattern.quote(literals.getFirst()))) {
- if (!token.isEmpty()) {
- attrValues.addAll(split(token, literals.subList(1, literals.size())));
- }
- }
- }
-
- return attrValues;
- }
-
- protected final PlainSchemaDAO plainSchemaDAO;
-
- protected final AnySearchDAO anySearchDAO;
-
- public AnyFinder(final PlainSchemaDAO plainSchemaDAO, final AnySearchDAO anySearchDAO) {
- this.plainSchemaDAO = plainSchemaDAO;
- this.anySearchDAO = anySearchDAO;
- }
-
- @Transactional(readOnly = true)
- public <A extends Any> List<A> findByDerAttrValue(
- final AnyTypeKind anyTypeKind,
- final String expression,
- final String value,
- final boolean ignoreCaseMatch) {
-
- Parser parser = new Parser(expression);
-
- // Schema keys
- List<String> identifiers = new ArrayList<>();
-
- // Literals
- List<String> literals = new ArrayList<>();
-
- // Get schema keys and literals
- for (Token token = parser.getNextToken(); token != null && StringUtils.isNotBlank(token.toString());
- token = parser.getNextToken()) {
-
- if (token.kind == ParserConstants.STRING_LITERAL) {
- literals.add(token.toString().substring(1, token.toString().length() - 1));
- }
-
- if (token.kind == ParserConstants.IDENTIFIER) {
- identifiers.add(token.toString());
- }
- }
-
- // Sort literals in order to process later literals included into others
- literals.sort(LITERAL_COMPARATOR);
-
- // Split value on provided literals
- List<String> attrValues = split(value, literals);
-
- if (attrValues.size() != identifiers.size()) {
- LOG.error("Ambiguous JEXL expression resolution: literals and values have different size");
- return List.of();
- }
-
- List<SearchCond> andConditions = new ArrayList<>();
-
- // Contains used identifiers in order to avoid replications
- Set<String> used = new HashSet<>();
-
- // Create several clauses: one for eanch identifiers
- for (int i = 0; i < identifiers.size() && !used.contains(identifiers.get(i)); i++) {
- used.add(identifiers.get(i));
-
- AttrCond cond = plainSchemaDAO.existsById(identifiers.get(i))
- ? new AttrCond()
- : new AnyCond();
- cond.setType(ignoreCaseMatch ? AttrCond.Type.IEQ : AttrCond.Type.EQ);
- cond.setSchema(identifiers.get(i));
- cond.setExpression(attrValues.get(i));
- andConditions.add(SearchCond.of(cond));
- }
-
- LOG.debug("Generated search {} conditions: {}", anyTypeKind, andConditions);
-
- return andConditions.isEmpty() ? List.of() : anySearchDAO.search(SearchCond.and(andConditions), anyTypeKind);
- }
-}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MariaDBPersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MariaDBPersistenceContext.java
index 4f3525942e..61a57aeaee 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MariaDBPersistenceContext.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MariaDBPersistenceContext.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.util.TimeZone;
import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -31,7 +30,9 @@ import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.persistence.jpa.dao.MariaDBJPAAnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.dao.MariaDBJPARealmSearchDAO;
import org.apache.syncope.core.persistence.jpa.dao.repo.MariaDBPlainSchemaRepoExtImpl;
import org.apache.syncope.core.persistence.jpa.dao.repo.PlainSchemaRepoExt;
import org.apache.syncope.core.persistence.jpa.entity.MariaDBEntityFactory;
@@ -83,7 +84,6 @@ public class MariaDBPersistenceContext {
final @Lazy EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
return new MariaDBJPAAnySearchDAO(
@@ -95,10 +95,30 @@ public class MariaDBPersistenceContext {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
+ @ConditionalOnMissingBean
+ @Bean
+ public RealmSearchDAO realmSearchDAO(
+ final EntityManager entityManager,
+ final @Lazy PlainSchemaDAO plainSchemaDAO,
+ final @Lazy UserDAO userDAO,
+ final @Lazy GroupDAO groupDAO,
+ final @Lazy EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
+ final RealmUtils realmUtils) {
+
+ return new MariaDBJPARealmSearchDAO(
+ entityManager,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ realmUtils);
+ }
+
@ConditionalOnMissingBean
@Bean
public PlainSchemaRepoExt plainSchemaRepoExt(
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MySQLPersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MySQLPersistenceContext.java
index 7544de272c..a90b1a575e 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MySQLPersistenceContext.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/MySQLPersistenceContext.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.util.TimeZone;
import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
@@ -31,7 +30,9 @@ import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.persistence.jpa.dao.MySQLJPAAnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.dao.MySQLJPARealmSearchDAO;
import org.apache.syncope.core.persistence.jpa.dao.repo.MySQLPlainSchemaRepoExtImpl;
import org.apache.syncope.core.persistence.jpa.dao.repo.PlainSchemaRepoExt;
import org.apache.syncope.core.persistence.jpa.entity.MySQLEntityFactory;
@@ -83,7 +84,6 @@ public class MySQLPersistenceContext {
final @Lazy EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
return new MySQLJPAAnySearchDAO(
@@ -95,10 +95,30 @@ public class MySQLPersistenceContext {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
+ @ConditionalOnMissingBean
+ @Bean
+ public RealmSearchDAO realmSearchDAO(
+ final EntityManager entityManager,
+ final @Lazy PlainSchemaDAO plainSchemaDAO,
+ final @Lazy UserDAO userDAO,
+ final @Lazy GroupDAO groupDAO,
+ final @Lazy EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
+ final RealmUtils realmUtils) {
+
+ return new MySQLJPARealmSearchDAO(
+ entityManager,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ realmUtils);
+ }
+
@ConditionalOnMissingBean
@Bean
public PlainSchemaRepoExt plainSchemaRepoExt(
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OraclePersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OraclePersistenceContext.java
index ea26e5fc89..d49afa29b4 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OraclePersistenceContext.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/OraclePersistenceContext.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
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;
@@ -30,7 +29,9 @@ import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.persistence.jpa.dao.OracleJPAAnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.dao.OracleJPARealmSearchDAO;
import org.apache.syncope.core.persistence.jpa.dao.repo.OraclePlainSchemaRepoExtImpl;
import org.apache.syncope.core.persistence.jpa.dao.repo.PlainSchemaRepoExt;
import org.apache.syncope.core.persistence.jpa.entity.OracleEntityFactory;
@@ -62,7 +63,6 @@ public class OraclePersistenceContext {
final @Lazy EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
return new OracleJPAAnySearchDAO(
@@ -74,10 +74,30 @@ public class OraclePersistenceContext {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
+ @ConditionalOnMissingBean
+ @Bean
+ public RealmSearchDAO realmSearchDAO(
+ final EntityManager entityManager,
+ final @Lazy PlainSchemaDAO plainSchemaDAO,
+ final @Lazy UserDAO userDAO,
+ final @Lazy GroupDAO groupDAO,
+ final @Lazy EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
+ final RealmUtils realmUtils) {
+
+ return new OracleJPARealmSearchDAO(
+ entityManager,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ realmUtils);
+ }
+
@ConditionalOnMissingBean
@Bean
public PlainSchemaRepoExt plainSchemaRepoExt(
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PGPersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PGPersistenceContext.java
index 4fbad459f6..9c35cfef8c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PGPersistenceContext.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PGPersistenceContext.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
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;
@@ -30,7 +29,9 @@ import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.persistence.jpa.dao.PGJPAAnySearchDAO;
+import org.apache.syncope.core.persistence.jpa.dao.PGJPARealmSearchDAO;
import org.apache.syncope.core.persistence.jpa.dao.repo.PGPlainSchemaRepoExtImpl;
import org.apache.syncope.core.persistence.jpa.dao.repo.PlainSchemaRepoExt;
import org.apache.syncope.core.persistence.jpa.entity.PGEntityFactory;
@@ -63,7 +64,6 @@ public class PGPersistenceContext {
final @Lazy EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
return new PGJPAAnySearchDAO(
@@ -75,10 +75,30 @@ public class PGPersistenceContext {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
+ @ConditionalOnMissingBean
+ @Bean
+ public RealmSearchDAO realmSearchDAO(
+ final EntityManager entityManager,
+ final @Lazy PlainSchemaDAO plainSchemaDAO,
+ final @Lazy UserDAO userDAO,
+ final @Lazy GroupDAO groupDAO,
+ final @Lazy EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
+ final RealmUtils realmUtils) {
+
+ return new PGJPARealmSearchDAO(
+ entityManager,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ realmUtils);
+ }
+
@ConditionalOnMissingBean
@Bean
public PlainSchemaRepoExt plainSchemaRepoExt(
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
index ad9b5e6445..c0abfd5597 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/PersistenceContext.java
@@ -85,7 +85,6 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
import org.apache.syncope.core.persistence.common.CommonPersistenceContext;
import org.apache.syncope.core.persistence.common.RuntimeDomainLoader;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.jpa.content.XMLContentExporter;
import org.apache.syncope.core.persistence.jpa.content.XMLContentLoader;
import org.apache.syncope.core.persistence.jpa.dao.JPAAnyMatchDAO;
@@ -97,7 +96,6 @@ import org.apache.syncope.core.persistence.jpa.dao.JPAOIDCJWKSDAO;
import org.apache.syncope.core.persistence.jpa.dao.JPAPersistenceInfoDAO;
import org.apache.syncope.core.persistence.jpa.dao.JPAPolicyDAO;
import org.apache.syncope.core.persistence.jpa.dao.JPARealmDAO;
-import org.apache.syncope.core.persistence.jpa.dao.JPARealmSearchDAO;
import org.apache.syncope.core.persistence.jpa.dao.JPATaskDAO;
import org.apache.syncope.core.persistence.jpa.dao.JPATaskExecDAO;
import org.apache.syncope.core.persistence.jpa.dao.repo.AccessTokenRepo;
@@ -408,16 +406,14 @@ public class PersistenceContext {
final @Lazy UserDAO userDAO,
final @Lazy GroupDAO groupDAO,
final EntityManager entityManager,
- final AnyChecker anyChecker,
- final AnyFinder anyFinder) {
+ final AnyChecker anyChecker) {
return new AnyObjectRepoExtImpl(
anyUtilsFactory,
userDAO,
groupDAO,
entityManager,
- anyChecker,
- anyFinder);
+ anyChecker);
}
@ConditionalOnMissingBean
@@ -625,8 +621,7 @@ public class PersistenceContext {
final @Lazy UserDAO userDAO,
final @Lazy AnyObjectDAO anyObjectDAO,
final EntityManager entityManager,
- final AnyChecker anyChecker,
- final AnyFinder anyFinder) {
+ final AnyChecker anyChecker) {
return new GroupRepoExtImpl(
anyUtilsFactory,
@@ -635,8 +630,7 @@ public class PersistenceContext {
userDAO,
anyObjectDAO,
entityManager,
- anyChecker,
- anyFinder);
+ anyChecker);
}
@ConditionalOnMissingBean
@@ -767,12 +761,6 @@ public class PersistenceContext {
return new JPARealmDAO(roleDAO, realmSearchDAO, plainSchemaDAO, publisher, entityManager, realmChecker);
}
- @ConditionalOnMissingBean
- @Bean
- public RealmSearchDAO realmSearchDAO(final EntityManager entityManager) {
- return new JPARealmSearchDAO(entityManager);
- }
-
@ConditionalOnMissingBean
@Bean
public RelationshipTypeRepoExt relationshipTypeRepoExt(final EntityManager entityManager) {
@@ -952,8 +940,7 @@ public class PersistenceContext {
final DelegationDAO delegationDAO,
final FIQLQueryDAO fiqlQueryDAO,
final EntityManager entityManager,
- final AnyChecker anyChecker,
- final AnyFinder anyFinder) {
+ final AnyChecker anyChecker) {
return new UserRepoExtImpl(
anyUtilsFactory,
@@ -964,8 +951,7 @@ public class PersistenceContext {
fiqlQueryDAO,
securityProperties,
entityManager,
- anyChecker,
- anyFinder);
+ anyChecker);
}
@ConditionalOnMissingBean
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
index 0d0e16570e..e08db492b6 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/content/XMLContentExporter.java
@@ -77,7 +77,6 @@ import org.apache.syncope.core.persistence.jpa.entity.JPAAuditEvent;
import org.apache.syncope.core.persistence.jpa.entity.JPAJobStatus;
import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;
@@ -367,7 +366,7 @@ public class XMLContentExporter extends AbstractXMLContentExporter {
if (tableName.equalsIgnoreCase(JPARealm.TABLE)) {
List<Map<String, String>> realmRows = new ArrayList<>(rows);
rows.clear();
- realmSearchDAO.findDescendants(SyncopeConstants.ROOT_REALM, null, Pageable.unpaged()).
+ realmSearchDAO.findDescendants(SyncopeConstants.ROOT_REALM, null).
forEach(realm -> realmRows.stream().filter(row -> {
String id = Optional.ofNullable(row.get("ID")).orElseGet(() -> row.get("id"));
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java
index 8db331780a..53892a8393 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAAnySearchDAO.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Query;
import java.util.ArrayList;
import java.util.HashMap;
@@ -28,10 +27,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Supplier;
import java.util.stream.Collectors;
-import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.syncope.common.lib.SyncopeClientException;
@@ -66,9 +62,6 @@ import org.apache.syncope.core.persistence.api.entity.PlainSchema;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.persistence.common.dao.AbstractAnySearchDAO;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
-import org.hibernate.dialect.OracleDialect;
-import org.hibernate.internal.SessionFactoryImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@@ -94,8 +87,6 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
+ "lastChangeDate,lastModifier,status,changePwdDate,cipherAlgorithm,failedLogins,"
+ "lastLoginDate,mustChangePassword,suspended,username";
- private static final Map<String, Boolean> IS_ORACLE = new ConcurrentHashMap<>();
-
protected static int setParameter(final List<Object> parameters, final Object parameter) {
parameters.add(parameter);
return parameters.size();
@@ -111,16 +102,6 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
}
}
- protected static Supplier<SyncopeClientException> syncopeClientException(final String message) {
- return () -> {
- SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchParameters);
- sce.getElements().add(message);
- return sce;
- };
- }
-
- protected final EntityManagerFactory entityManagerFactory;
-
protected final EntityManager entityManager;
protected AbstractJPAAnySearchDAO(
@@ -132,7 +113,6 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
final EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
super(
@@ -144,17 +124,9 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
entityFactory,
anyUtilsFactory,
validator);
- this.entityManagerFactory = entityManagerFactory;
this.entityManager = entityManager;
}
- protected boolean isOracle() {
- return IS_ORACLE.computeIfAbsent(
- AuthContextUtils.getDomain(),
- k -> entityManagerFactory.unwrap(SessionFactoryImpl.class).getJdbcServices().
- getDialect() instanceof OracleDialect);
- }
-
protected SearchSupport.SearchView defaultSV(final SearchSupport svs) {
return svs.field();
}
@@ -505,7 +477,7 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
final boolean not,
final List<Object> parameters) {
- // activate ignoreCase only for EQ and LIKE operators
+ // activate ignoreCase only for ILIKE and IEQ operators
boolean ignoreCase = AttrCond.Type.ILIKE == cond.getType() || AttrCond.Type.IEQ == cond.getType();
String left = column;
@@ -528,7 +500,7 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
} else {
clause.append('?').append(setParameter(parameters, cond.getExpression()));
}
- if (isOracle()) {
+ if (this instanceof OracleJPAAnySearchDAO) {
clause.append(" ESCAPE '\\'");
}
} else {
@@ -598,74 +570,12 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
: from.alias() + ".schema_id='" + schema.getKey() + "' AND " + clause);
}
- protected AttrCondQuery getQuery(
- final AttrCond cond,
- final boolean not,
- final CheckResult<AttrCond> checked,
- final List<Object> parameters,
- final SearchSupport svs) {
-
- // normalize NULL / NOT NULL checks
- if (not) {
- if (cond.getType() == AttrCond.Type.ISNULL) {
- cond.setType(AttrCond.Type.ISNOTNULL);
- } else if (cond.getType() == AttrCond.Type.ISNOTNULL) {
- cond.setType(AttrCond.Type.ISNULL);
- }
- }
-
- SearchSupport.SearchView sv = checked.schema().isUniqueConstraint()
- ? svs.asSearchViewSupport().uniqueAttr()
- : svs.asSearchViewSupport().attr();
-
- switch (cond.getType()) {
- case ISNOTNULL -> {
- return new AttrCondQuery(true, new AnySearchNode.Leaf(
- sv,
- sv.alias() + ".schema_id='" + checked.schema().getKey() + "'"));
- }
-
- case ISNULL -> {
- String clause = new StringBuilder(anyId(svs)).append(" NOT IN ").
- append('(').
- append("SELECT DISTINCT any_id FROM ").
- append(sv.name()).
- append(" WHERE schema_id=").append("'").append(checked.schema().getKey()).append("'").
- append(')').toString();
- return new AttrCondQuery(true, new AnySearchNode.Leaf(defaultSV(svs), clause));
- }
-
- default -> {
- AnySearchNode.Leaf node;
- if (not && checked.schema().isMultivalue()) {
- AnySearchNode.Leaf notNode = fillAttrQuery(
- sv.alias() + "." + key(checked.schema().getType()),
- sv,
- checked.value(),
- checked.schema(),
- cond,
- false,
- parameters);
- node = new AnySearchNode.Leaf(
- sv,
- anyId(svs) + " NOT IN ("
- + "SELECT any_id FROM " + sv.name()
- + " WHERE " + notNode.getClause().replace(sv.alias() + ".", "")
- + ")");
- } else {
- node = fillAttrQuery(
- sv.alias() + "." + key(checked.schema().getType()),
- sv,
- checked.value(),
- checked.schema(),
- cond,
- not,
- parameters);
- }
- return new AttrCondQuery(true, node);
- }
- }
- }
+ protected abstract AttrCondQuery getQuery(
+ AttrCond cond,
+ boolean not,
+ CheckResult<AttrCond> checked,
+ List<Object> parameters,
+ SearchSupport svs);
protected AnySearchNode getQuery(
final AnyCond cond,
@@ -681,7 +591,11 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
cond.setExpression(realm.getKey());
}
- CheckResult<AnyCond> checked = check(cond, svs.anyTypeKind);
+ CheckResult<AnyCond> checked = check(
+ cond,
+ anyUtilsFactory.getInstance(svs.anyTypeKind).getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
return switch (checked.cond().getType()) {
case ISNULL ->
@@ -742,7 +656,8 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
return noRealm;
});
- realmKeys.addAll(realmSearchDAO.findDescendants(realm.getFullPath(), base.getFullPath()));
+ realmKeys.addAll(realmSearchDAO.findDescendants(realm.getFullPath(), base.getFullPath()).
+ stream().map(Realm::getKey).toList());
}));
} else {
if (adminRealms.stream().anyMatch(r -> r.startsWith(base.getFullPath()))) {
@@ -810,7 +725,7 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
protected String buildWhere(final List<String> where, final AnySearchNode root) {
return where.stream().
map(w -> "(" + w + ")").
- collect(Collectors.joining(" " + root.getType().name() + " "));
+ collect(Collectors.joining(' ' + root.getType().name() + ' '));
}
protected String buildCountQuery(
@@ -833,15 +748,14 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
Map<SearchSupport.SearchView, Boolean> counters = new HashMap<>();
visitNode(root, counters, from, where, svs);
- StringBuilder queryString = new StringBuilder("SELECT COUNT(DISTINCT ").append(anyId(svs)).append(") ");
-
- queryString.append("FROM ").append(buildFrom(from, queryInfo.plainSchemas(), null));
-
- queryString.append(" WHERE ").append(buildWhere(where, root));
+ String queryString = new StringBuilder("SELECT COUNT(DISTINCT ").append(anyId(svs)).append(") ").
+ append("FROM ").append(buildFrom(from, queryInfo.plainSchemas(), null)).
+ append(" WHERE ").append(buildWhere(where, root)).
+ toString();
LOG.debug("Query: {}, parameters: {}", queryString, parameters);
- return queryString.toString();
+ return queryString;
}
@Override
@@ -879,40 +793,13 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
return ((Number) countQuery.getSingleResult()).intValue();
}
- protected void parseOrderByForPlainSchema(
- final SearchSupport svs,
- final OrderBySupport obs,
- final OrderBySupport.Item item,
- final Sort.Order clause,
- final PlainSchema schema,
- final String fieldName) {
-
- // keep track of involvement of non-mandatory schemas in the order by clauses
- obs.nonMandatorySchemas = !"true".equals(schema.getMandatoryCondition());
-
- if (schema.isUniqueConstraint()) {
- obs.views.add(svs.asSearchViewSupport().uniqueAttr());
-
- item.select = new StringBuilder().
- append(svs.asSearchViewSupport().uniqueAttr().alias()).append('.').
- append(key(schema.getType())).
- append(" AS ").append(fieldName).toString();
- item.where = new StringBuilder().
- append(svs.asSearchViewSupport().uniqueAttr().alias()).
- append(".schema_id='").append(fieldName).append("'").toString();
- item.orderBy = fieldName + ' ' + clause.getDirection().name();
- } else {
- obs.views.add(svs.asSearchViewSupport().attr());
-
- item.select = new StringBuilder().
- append(svs.asSearchViewSupport().attr().alias()).append('.').append(key(schema.getType())).
- append(" AS ").append(fieldName).toString();
- item.where = new StringBuilder().
- append(svs.asSearchViewSupport().attr().alias()).
- append(".schema_id='").append(fieldName).append("'").toString();
- item.orderBy = fieldName + ' ' + clause.getDirection().name();
- }
- }
+ protected abstract void parseOrderByForPlainSchema(
+ SearchSupport svs,
+ OrderBySupport obs,
+ OrderBySupport.Item item,
+ Sort.Order clause,
+ PlainSchema schema,
+ String fieldName);
protected void parseOrderByForField(
final SearchSupport svs,
@@ -956,7 +843,7 @@ abstract class AbstractJPAAnySearchDAO extends AbstractAnySearchDAO {
String fieldName = "key".equals(clause.getProperty()) ? "id" : clause.getProperty();
// Adjust field name to column name
- if (ArrayUtils.contains(RELATIONSHIP_FIELDS, fieldName)) {
+ if (RELATIONSHIP_FIELDS.contains(fieldName)) {
fieldName += "_id";
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPARealmSearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPARealmSearchDAO.java
new file mode 100644
index 0000000000..a6ce4cbdfb
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPARealmSearchDAO.java
@@ -0,0 +1,541 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.NoResultException;
+import jakarta.persistence.Query;
+import jakarta.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+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.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+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.Realm;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
+import org.apache.syncope.core.persistence.common.dao.AbstractRealmSearchDAO;
+import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.transaction.annotation.Transactional;
+
+public abstract class AbstractJPARealmSearchDAO extends AbstractRealmSearchDAO {
+
+ protected record QueryInfo(RealmSearchNode node, Set<String> plainSchemas) {
+
+ }
+
+ protected record AttrCondQuery(Boolean addPlainSchemas, RealmSearchNode node) {
+
+ }
+
+ protected static int setParameter(final List<Object> parameters, final Object parameter) {
+ parameters.add(parameter);
+ return parameters.size();
+ }
+
+ protected static void fillWithParameters(final Query query, final List<Object> parameters) {
+ for (int i = 0; i < parameters.size(); i++) {
+ if (parameters.get(i) instanceof Boolean aBoolean) {
+ query.setParameter(i + 1, aBoolean ? 1 : 0);
+ } else {
+ query.setParameter(i + 1, parameters.get(i));
+ }
+ }
+ }
+
+ protected final EntityManager entityManager;
+
+ protected final RealmUtils realmUtils;
+
+ protected AbstractJPARealmSearchDAO(
+ final EntityManager entityManager,
+ final PlainSchemaDAO plainSchemaDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
+ final RealmUtils realmUtils) {
+
+ super(plainSchemaDAO, userDAO, groupDAO, entityFactory, validator);
+
+ this.entityManager = entityManager;
+ this.realmUtils = realmUtils;
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public Optional<Realm> findByFullPath(final String fullPath) {
+ if (StringUtils.isBlank(fullPath)
+ || (!SyncopeConstants.ROOT_REALM.equals(fullPath)
+ && !RealmDAO.PATH_PATTERN.matcher(fullPath).matches())) {
+
+ throw new MalformedPathException(fullPath);
+ }
+
+ TypedQuery<Realm> query = entityManager.createQuery(
+ "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e.fullPath=:fullPath", Realm.class);
+ query.setParameter("fullPath", fullPath);
+
+ Realm result = null;
+ try {
+ result = query.getSingleResult();
+ } catch (NoResultException e) {
+ LOG.debug("Realm with fullPath {} not found", fullPath, e);
+ }
+
+ return Optional.ofNullable(result);
+ }
+
+ @Override
+ public List<Realm> findByName(final String name) {
+ TypedQuery<Realm> query = entityManager.createQuery(
+ "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e.name=:name", Realm.class);
+ query.setParameter("name", name);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List<Realm> findChildren(final Realm realm) {
+ TypedQuery<Realm> query = entityManager.createQuery(
+ "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e.parent=:realm", Realm.class);
+ query.setParameter("realm", realm);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public List<Realm> findDescendants(final String base, final String prefix) {
+ List<Object> parameters = new ArrayList<>();
+
+ StringBuilder queryString = new StringBuilder("SELECT e FROM ").
+ append(JPARealm.class.getSimpleName()).
+ append(" e WHERE (e.fullPath=?").append(setParameter(parameters, base)).
+ append(" OR e.fullPath LIKE ?").append(setParameter(
+ parameters,
+ SyncopeConstants.ROOT_REALM.equals(base) ? "/%" : base + "/%")).
+ append(") ");
+ if (prefix != null) {
+ queryString.append("AND (e.fullPath=?").append(setParameter(parameters, prefix)).
+ append(" OR e.fullPath LIKE ?").append(setParameter(
+ parameters,
+ SyncopeConstants.ROOT_REALM.equals(prefix) ? "/%" : prefix + "/%")).
+ append(") ");
+ }
+ queryString.append("ORDER BY e.fullPath");
+
+ TypedQuery<Realm> query = entityManager.createQuery(queryString.toString(), Realm.class);
+
+ fillWithParameters(query, parameters);
+
+ return query.getResultList();
+ }
+
+ // ------------------------------------------ //
+ protected Optional<RealmSearchNode> getQueryForCustomConds(
+ final SearchCond cond,
+ final boolean not,
+ final List<Object> parameters) {
+
+ return Optional.empty();
+ }
+
+ protected Optional<QueryInfo> getQuery(final SearchCond cond, final List<Object> parameters) {
+ if (cond == null) {
+ return Optional.empty();
+ }
+
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ Optional<RealmSearchNode> node = Optional.empty();
+ Set<String> plainSchemas = new HashSet<>();
+
+ switch (cond.getType()) {
+ case LEAF:
+ case NOT_LEAF:
+ node = cond.asLeaf(AnyCond.class).
+ map(anyCond -> getQuery(anyCond, not, parameters)).
+ or(() -> cond.asLeaf(AttrCond.class).
+ map(attrCond -> {
+ CheckResult<AttrCond> checked = check(attrCond);
+ AttrCondQuery query = getQuery(attrCond, not, checked, parameters);
+ if (query.addPlainSchemas()) {
+ plainSchemas.add(checked.schema().getKey());
+ }
+ return query.node();
+ }));
+
+ if (node.isEmpty()) {
+ node = getQueryForCustomConds(cond, not, parameters);
+ }
+ break;
+
+ case AND:
+ RealmSearchNode andNode = new RealmSearchNode(RealmSearchNode.Type.AND);
+
+ getQuery(cond.getLeft(), parameters).ifPresent(left -> {
+ andNode.add(left.node());
+ plainSchemas.addAll(left.plainSchemas());
+ });
+
+ getQuery(cond.getRight(), parameters).ifPresent(right -> {
+ andNode.add(right.node());
+ plainSchemas.addAll(right.plainSchemas());
+ });
+
+ if (!andNode.getChildren().isEmpty()) {
+ node = Optional.of(andNode);
+ }
+ break;
+
+ case OR:
+ RealmSearchNode orNode = new RealmSearchNode(RealmSearchNode.Type.OR);
+
+ getQuery(cond.getLeft(), parameters).ifPresent(left -> {
+ orNode.add(left.node());
+ plainSchemas.addAll(left.plainSchemas());
+ });
+
+ getQuery(cond.getRight(), parameters).ifPresent(right -> {
+ orNode.add(right.node());
+ plainSchemas.addAll(right.plainSchemas());
+ });
+
+ if (!orNode.getChildren().isEmpty()) {
+ node = Optional.of(orNode);
+ }
+ break;
+
+ default:
+ }
+
+ return node.map(n -> new QueryInfo(n, plainSchemas));
+ }
+
+ protected abstract AttrCondQuery getQuery(
+ AttrCond cond,
+ boolean not,
+ CheckResult<AttrCond> checked,
+ List<Object> parameters);
+
+ protected RealmSearchNode getQuery(final AnyCond cond, final boolean not, final List<Object> parameters) {
+ CheckResult<AnyCond> checked = check(
+ cond,
+ realmUtils.getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
+
+ return switch (checked.cond().getType()) {
+ case ISNULL ->
+ new RealmSearchNode.Leaf("r." + checked.cond().getSchema() + (not ? " IS NOT NULL" : " IS NULL"));
+
+ case ISNOTNULL ->
+ new RealmSearchNode.Leaf("r." + checked.cond().getSchema() + (not ? " IS NULL" : " IS NOT NULL"));
+
+ default ->
+ fillAttrQuery(
+ "r." + checked.cond().getSchema(),
+ checked.value(),
+ checked.schema(),
+ checked.cond(),
+ not,
+ parameters);
+ };
+ }
+
+ protected RealmSearchNode.Leaf fillAttrQuery(
+ final String column,
+ final PlainAttrValue attrValue,
+ final PlainSchema schema,
+ final AttrCond cond,
+ final boolean not,
+ final List<Object> parameters) {
+
+ boolean ignoreCase = AttrCond.Type.ILIKE == cond.getType() || AttrCond.Type.IEQ == cond.getType();
+
+ String left = column;
+ if (ignoreCase && (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum)) {
+ left = "LOWER(" + left + ')';
+ }
+
+ StringBuilder clause = new StringBuilder(left);
+ switch (cond.getType()) {
+
+ case ILIKE:
+ case LIKE:
+ if (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) {
+ if (not) {
+ clause.append(" NOT");
+ }
+ clause.append(" LIKE ");
+ if (ignoreCase) {
+ clause.append("LOWER(?").append(setParameter(parameters, cond.getExpression())).append(')');
+ } else {
+ clause.append('?').append(setParameter(parameters, cond.getExpression()));
+ }
+ if (this instanceof OracleJPARealmSearchDAO) {
+ clause.append(" ESCAPE '\\'");
+ }
+ } else {
+ LOG.error("LIKE is only compatible with string or enum schemas");
+ return new RealmSearchNode.Leaf(ALWAYS_FALSE_CLAUSE);
+ }
+ break;
+
+ case IEQ:
+ case EQ:
+ default:
+ clause.append(not ? "<>" : "=");
+ if (ignoreCase
+ && (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum)) {
+ clause.append("LOWER(?").append(setParameter(parameters, attrValue.getValue())).append(')');
+ } else {
+ clause.append('?').append(setParameter(parameters, attrValue.getValue()));
+ }
+ break;
+
+ case GE:
+ clause.append(not ? "<" : ">=");
+ clause.append('?').append(setParameter(parameters, attrValue.getValue()));
+ break;
+
+ case GT:
+ clause.append(not ? "<=" : ">");
+ clause.append('?').append(setParameter(parameters, attrValue.getValue()));
+ break;
+
+ case LE:
+ clause.append(not ? ">" : "<=");
+ clause.append('?').append(setParameter(parameters, attrValue.getValue()));
+ break;
+
+ case LT:
+ clause.append(not ? ">=" : "<");
+ clause.append('?').append(setParameter(parameters, attrValue.getValue()));
+ break;
+ }
+
+ return new RealmSearchNode.Leaf(
+ cond instanceof AnyCond
+ ? clause.toString()
+ : "r.schema_id='" + schema.getKey() + "' AND " + clause);
+ }
+
+ protected void visitNode(final RealmSearchNode node, final List<String> where) {
+ node.asLeaf().ifPresentOrElse(
+ leaf -> where.add(leaf.getClause()),
+ () -> {
+ List<String> nodeWhere = new ArrayList<>();
+ node.getChildren().forEach(child -> visitNode(child, nodeWhere));
+ String op = " " + node.getType().name() + " ";
+ where.add(nodeWhere.stream().
+ map(w -> w.contains(" AND ") || w.contains(" OR ") ? "(" + w + ")" : w).
+ collect(Collectors.joining(op)));
+ });
+ }
+
+ protected String buildFrom(final Set<String> plainSchemas, final OrderBySupport obs) {
+ return JPARealm.TABLE + " r";
+ }
+
+ protected String buildWhere(final Set<String> bases, final QueryInfo queryInfo, final List<Object> parameters) {
+ String fullPaths = bases.stream().
+ map(base -> "r.fullPath=?" + setParameter(parameters, base)
+ + " OR r.fullPath LIKE ?" + setParameter(
+ parameters, SyncopeConstants.ROOT_REALM.equals(base) ? "/%" : base + "/%")).
+ collect(Collectors.joining(" OR "));
+
+ RealmSearchNode root;
+ if (queryInfo.node().getType() == RealmSearchNode.Type.AND) {
+ root = queryInfo.node();
+ } else {
+ root = new RealmSearchNode(RealmSearchNode.Type.AND);
+ root.add(queryInfo.node());
+ }
+
+ List<String> where = new ArrayList<>();
+ visitNode(root, where);
+
+ return "(" + fullPaths + ')'
+ + " AND (" + where.stream().
+ map(w -> w.contains(" AND ") || w.contains(" OR ") ? "(" + w + ")" : w).
+ collect(Collectors.joining(' ' + root.getType().name() + ' '))
+ + ')';
+ }
+
+ @Override
+ protected long doCount(final Set<String> bases, final SearchCond cond) {
+ List<Object> parameters = new ArrayList<>();
+
+ QueryInfo queryInfo = getQuery(cond, parameters).orElse(null);
+ if (queryInfo == null) {
+ LOG.error("Invalid search condition: {}", cond);
+ return 0;
+ }
+
+ String queryString = new StringBuilder("SELECT COUNT(DISTINCT r.id)").
+ append(" FROM ").append(buildFrom(queryInfo.plainSchemas(), null)).
+ append(" WHERE ").append(buildWhere(bases, queryInfo, parameters)).
+ toString();
+
+ LOG.debug("Query: {}, parameters: {}", queryString, parameters);
+
+ Query query = entityManager.createNativeQuery(queryString);
+ fillWithParameters(query, parameters);
+
+ return ((Number) query.getSingleResult()).longValue();
+ }
+
+ protected abstract void parseOrderByForPlainSchema(
+ OrderBySupport obs,
+ OrderBySupport.Item item,
+ Sort.Order clause,
+ PlainSchema schema,
+ String fieldName);
+
+ protected void parseOrderByForField(
+ final OrderBySupport.Item item,
+ final String fieldName,
+ final Sort.Order clause) {
+
+ item.select = "r." + fieldName;
+ item.where = StringUtils.EMPTY;
+ item.orderBy = "r." + fieldName + ' ' + clause.getDirection().name();
+ }
+
+ protected void parseOrderByForCustom(
+ final Sort.Order clause,
+ final OrderBySupport.Item item,
+ final OrderBySupport obs) {
+
+ // do nothing by default, meant for subclasses
+ }
+
+ protected OrderBySupport parseOrderBy(final List<Sort.Order> orderBy) {
+ OrderBySupport obs = new OrderBySupport();
+
+ Set<String> orderByUniquePlainSchemas = new HashSet<>();
+ Set<String> orderByNonUniquePlainSchemas = new HashSet<>();
+ orderBy.forEach(clause -> {
+ OrderBySupport.Item item = new OrderBySupport.Item();
+
+ parseOrderByForCustom(clause, item, obs);
+
+ if (item.isEmpty()) {
+ realmUtils.getField(clause.getProperty()).ifPresentOrElse(
+ field -> {
+ // Manage difference among external key attribute and internal JPA @Id
+ String fieldName = "key".equals(clause.getProperty()) ? "id" : clause.getProperty();
+
+ // Adjust field name to column name
+ if (RELATIONSHIP_FIELDS.contains(fieldName)) {
+ fieldName += "_id";
+ }
+
+ parseOrderByForField(item, fieldName, clause);
+ },
+ () -> {
+ plainSchemaDAO.findById(clause.getProperty()).ifPresent(schema -> {
+ if (schema.isUniqueConstraint()) {
+ orderByUniquePlainSchemas.add(schema.getKey());
+ } else {
+ orderByNonUniquePlainSchemas.add(schema.getKey());
+ }
+ if (orderByUniquePlainSchemas.size() > 1 || orderByNonUniquePlainSchemas.size() > 1) {
+ throw syncopeClientException("Order by more than one attribute is not allowed; "
+ + "remove one from " + (orderByUniquePlainSchemas.size() > 1
+ ? orderByUniquePlainSchemas : orderByNonUniquePlainSchemas)).get();
+ }
+ parseOrderByForPlainSchema(obs, item, clause, schema, clause.getProperty());
+ });
+ });
+ }
+
+ if (item.isEmpty()) {
+ LOG.warn("Cannot build any valid clause from {}", clause);
+ } else {
+ obs.items.add(item);
+ }
+ });
+
+ return obs;
+ }
+
+ @Override
+ protected List<Realm> doSearch(final Set<String> bases, final SearchCond cond, final Pageable pageable) {
+ List<Object> parameters = new ArrayList<>();
+
+ QueryInfo queryInfo = getQuery(cond, parameters).orElse(null);
+ if (queryInfo == null) {
+ LOG.error("Invalid search condition: {}", cond);
+ return List.of();
+ }
+
+ OrderBySupport obs = parseOrderBy(pageable.getSort().toList());
+
+ StringBuilder queryString = new StringBuilder("SELECT DISTINCT r.id");
+ obs.items.forEach(item -> queryString.append(',').append(item.select));
+
+ queryString.append(" FROM ").append(buildFrom(queryInfo.plainSchemas(), obs)).
+ append(" WHERE ").append(buildWhere(bases, queryInfo, parameters)).
+ toString();
+
+ if (!obs.items.isEmpty()) {
+ queryString.append(" ORDER BY ").
+ append(obs.items.stream().map(item -> item.orderBy).collect(Collectors.joining(",")));
+ }
+
+ LOG.debug("Query: {}, parameters: {}", queryString, parameters);
+
+ Query query = entityManager.createNativeQuery(queryString.toString());
+
+ if (pageable.isPaged()) {
+ query.setFirstResult(pageable.getPageSize() * pageable.getPageNumber());
+ query.setMaxResults(pageable.getPageSize());
+ }
+
+ fillWithParameters(query, parameters);
+
+ @SuppressWarnings("unchecked")
+ List<String> keys = query.getResultList().stream().
+ map(key -> key instanceof Object[] array ? (String) (array)[0] : ((String) key)).
+ toList();
+
+ return keys.stream().map(k -> entityManager.find(JPARealm.class, k)).
+ filter(Objects::nonNull).map(Realm.class::cast).toList();
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
index 8228f71fdc..70b53a9937 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
@@ -246,7 +246,7 @@ public class JPARealmDAO implements RealmDAO {
return;
}
- realmSearchDAO.findDescendants(realm.getFullPath(), null, Pageable.unpaged()).forEach(toBeDeleted -> {
+ realmSearchDAO.findDescendants(realm.getFullPath(), null).forEach(toBeDeleted -> {
roleDAO.findByRealms(toBeDeleted).forEach(role -> role.getRealms().remove(toBeDeleted));
toBeDeleted.setParent(null);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmSearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmSearchDAO.java
deleted file mode 100644
index d3aef9eaa9..0000000000
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmSearchDAO.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.core.persistence.jpa.dao;
-
-import jakarta.persistence.EntityManager;
-import jakarta.persistence.NoResultException;
-import jakarta.persistence.Query;
-import jakarta.persistence.TypedQuery;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.Strings;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
-import org.apache.syncope.core.persistence.api.dao.RealmDAO;
-import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
-import org.apache.syncope.core.persistence.api.entity.Realm;
-import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.data.domain.Pageable;
-import org.springframework.transaction.annotation.Transactional;
-
-public class JPARealmSearchDAO implements RealmSearchDAO {
-
- protected static final Logger LOG = LoggerFactory.getLogger(RealmSearchDAO.class);
-
- protected static int setParameter(final List<Object> parameters, final Object parameter) {
- parameters.add(parameter);
- return parameters.size();
- }
-
- protected static StringBuilder buildDescendantsQuery(
- final Set<String> bases,
- final String keyword,
- final List<Object> parameters) {
-
- String basesClause = bases.stream().
- map(base -> "e.fullPath=?" + setParameter(parameters, base)
- + " OR e.fullPath LIKE ?" + setParameter(
- parameters, SyncopeConstants.ROOT_REALM.equals(base) ? "/%" : base + "/%")).
- collect(Collectors.joining(" OR "));
-
- StringBuilder queryString = new StringBuilder("SELECT e FROM ").
- append(JPARealm.class.getSimpleName()).append(" e ").
- append("WHERE (").append(basesClause).append(')');
-
- if (keyword != null) {
- queryString.append(" AND LOWER(e.name) LIKE ?").
- append(setParameter(parameters, "%" + keyword.replaceAll("_", "\\\\_").toLowerCase() + "%"));
- }
-
- return queryString;
- }
-
- protected final EntityManager entityManager;
-
- public JPARealmSearchDAO(final EntityManager entityManager) {
- this.entityManager = entityManager;
- }
-
- @Transactional(readOnly = true)
- @Override
- public Optional<Realm> findByFullPath(final String fullPath) {
- if (StringUtils.isBlank(fullPath)
- || (!SyncopeConstants.ROOT_REALM.equals(fullPath)
- && !RealmDAO.PATH_PATTERN.matcher(fullPath).matches())) {
-
- throw new MalformedPathException(fullPath);
- }
-
- TypedQuery<Realm> query = entityManager.createQuery(
- "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e.fullPath=:fullPath", Realm.class);
- query.setParameter("fullPath", fullPath);
-
- Realm result = null;
- try {
- result = query.getSingleResult();
- } catch (NoResultException e) {
- LOG.debug("Realm with fullPath {} not found", fullPath, e);
- }
-
- return Optional.ofNullable(result);
- }
-
- @Override
- public List<Realm> findByName(final String name) {
- TypedQuery<Realm> query = entityManager.createQuery(
- "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e.name=:name", Realm.class);
- query.setParameter("name", name);
-
- return query.getResultList();
- }
-
- @Override
- public List<Realm> findChildren(final Realm realm) {
- TypedQuery<Realm> query = entityManager.createQuery(
- "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e.parent=:realm", Realm.class);
- query.setParameter("realm", realm);
-
- return query.getResultList();
- }
-
- @Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
- }
-
- @Override
- public long countDescendants(final Set<String> bases, final String keyword) {
- List<Object> parameters = new ArrayList<>();
-
- StringBuilder queryString = buildDescendantsQuery(bases, keyword, parameters);
- Query query = entityManager.createQuery(Strings.CS.replaceOnce(
- queryString.toString(),
- "SELECT e ",
- "SELECT COUNT(e) "));
-
- for (int i = 1; i <= parameters.size(); i++) {
- query.setParameter(i, parameters.get(i - 1));
- }
-
- return ((Number) query.getSingleResult()).longValue();
- }
-
- @Override
- public List<Realm> findDescendants(final String base, final String keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
- }
-
- @Override
- public List<Realm> findDescendants(final Set<String> bases, final String keyword, final Pageable pageable) {
- List<Object> parameters = new ArrayList<>();
-
- StringBuilder queryString = buildDescendantsQuery(bases, keyword, parameters);
- TypedQuery<Realm> query = entityManager.createQuery(
- queryString.append(" ORDER BY e.fullPath").toString(), Realm.class);
-
- for (int i = 1; i <= parameters.size(); i++) {
- query.setParameter(i, parameters.get(i - 1));
- }
-
- if (pageable.isPaged()) {
- query.setFirstResult(pageable.getPageSize() * pageable.getPageNumber());
- query.setMaxResults(pageable.getPageSize());
- }
-
- return query.getResultList();
- }
-
- @Override
- public List<String> findDescendants(final String base, final String prefix) {
- List<Object> parameters = new ArrayList<>();
-
- StringBuilder queryString = buildDescendantsQuery(Set.of(base), null, parameters);
- TypedQuery<Realm> query = entityManager.createQuery(queryString.
- append(" AND (e.fullPath=?").
- append(setParameter(parameters, prefix)).
- append(" OR e.fullPath LIKE ?").
- append(setParameter(parameters, SyncopeConstants.ROOT_REALM.equals(prefix) ? "/%" : prefix + "/%")).
- append(')').
- append(" ORDER BY e.fullPath").toString(),
- Realm.class);
-
- for (int i = 1; i <= parameters.size(); i++) {
- query.setParameter(i, parameters.get(i - 1));
- }
-
- return query.getResultList().stream().map(Realm::getKey).toList();
- }
-}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
index 21c90912cf..c94ed7853a 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPATaskDAO.java
@@ -339,7 +339,7 @@ public class JPATaskDAO implements TaskDAO {
String realmKeysArg = AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.TASK_LIST).stream().
map(realmSearchDAO::findByFullPath).
filter(Optional::isPresent).
- flatMap(r -> realmSearchDAO.findDescendants(r.get().getFullPath(), null, Pageable.unpaged()).
+ flatMap(r -> realmSearchDAO.findDescendants(r.get().getFullPath(), null).
stream()).
map(Realm::getKey).
distinct().
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPAAnySearchDAO.java
index ab768e1d75..09647e3ed6 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPAAnySearchDAO.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
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.GroupDAO;
@@ -40,7 +39,6 @@ public class MariaDBJPAAnySearchDAO extends MySQLJPAAnySearchDAO {
final EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
super(
@@ -52,7 +50,6 @@ public class MariaDBJPAAnySearchDAO extends MySQLJPAAnySearchDAO {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPARealmSearchDAO.java
similarity index 59%
copy from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPAAnySearchDAO.java
copy to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPARealmSearchDAO.java
index ab768e1d75..1f734aa3e7 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MariaDBJPARealmSearchDAO.java
@@ -19,40 +19,24 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
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.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
-import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
-public class MariaDBJPAAnySearchDAO extends MySQLJPAAnySearchDAO {
+public class MariaDBJPARealmSearchDAO extends MySQLJPARealmSearchDAO {
- public MariaDBJPAAnySearchDAO(
- final RealmSearchDAO realmSearchDAO,
+ public MariaDBJPARealmSearchDAO(
+ final EntityManager entityManager,
+ final PlainSchemaDAO plainSchemaDAO,
final UserDAO userDAO,
final GroupDAO groupDAO,
- final AnyObjectDAO anyObjectDAO,
- final PlainSchemaDAO schemaDAO,
final EntityFactory entityFactory,
- final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
- final EntityManager entityManager) {
+ final RealmUtils realmUtils) {
- super(
- realmSearchDAO,
- userDAO,
- groupDAO,
- anyObjectDAO,
- schemaDAO,
- entityFactory,
- anyUtilsFactory,
- validator,
- entityManagerFactory,
- entityManager);
+ super(entityManager, plainSchemaDAO, userDAO, groupDAO, entityFactory, validator, realmUtils);
}
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPAAnySearchDAO.java
index e9c4f007b3..3aa62156eb 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPAAnySearchDAO.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
@@ -50,7 +49,6 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
final EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
super(
@@ -62,7 +60,6 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPARealmSearchDAO.java
similarity index 57%
copy from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPAAnySearchDAO.java
copy to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPARealmSearchDAO.java
index e9c4f007b3..46d5cf4f8c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MySQLJPARealmSearchDAO.java
@@ -4,14 +4,14 @@
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
+ * \"License\"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
@@ -19,56 +19,71 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.types.AttrSchemaType;
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.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
-import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
-import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
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.PlainSchema;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
+import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
import org.springframework.data.domain.Sort;
-public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
+public class MySQLJPARealmSearchDAO extends AbstractJPARealmSearchDAO {
- public MySQLJPAAnySearchDAO(
- final RealmSearchDAO realmSearchDAO,
+ protected static String from(final PlainSchema schema) {
+ String sqlType = switch (schema.getType()) {
+ case Long ->
+ "BIGINT";
+
+ case Double ->
+ "DOUBLE";
+
+ case Boolean ->
+ "VARCHAR(8)";
+
+ default ->
+ "VARCHAR(255)";
+ };
+
+ if (schema.isUniqueConstraint()) {
+ return "JSON_TABLE(r.plainAttrs, '$[*]' COLUMNS ("
+ + "schemaz VARCHAR(255) PATH '$.schema', "
+ + "uniqueValue " + sqlType + " PATH '$.uniqueValue." + key(schema.getType()) + "'"
+ + ")) AS " + schema.getKey();
+ }
+
+ return "JSON_TABLE(r.plainAttrs, '$[*]' COLUMNS ("
+ + "schemaz VARCHAR(255) PATH '$.schema', "
+ + "NESTED PATH '$.values[*]' COLUMNS ("
+ + "valuez " + sqlType + " PATH '$." + key(schema.getType()) + "'"
+ + "))) AS " + schema.getKey();
+ }
+
+ public MySQLJPARealmSearchDAO(
+ final EntityManager entityManager,
+ final PlainSchemaDAO plainSchemaDAO,
final UserDAO userDAO,
final GroupDAO groupDAO,
- final AnyObjectDAO anyObjectDAO,
- final PlainSchemaDAO schemaDAO,
final EntityFactory entityFactory,
- final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
- final EntityManager entityManager) {
-
- super(
- realmSearchDAO,
- userDAO,
- groupDAO,
- anyObjectDAO,
- schemaDAO,
- entityFactory,
- anyUtilsFactory,
- validator,
- entityManagerFactory,
- entityManager);
+ final RealmUtils realmUtils) {
+
+ super(entityManager, plainSchemaDAO, userDAO, groupDAO, entityFactory, validator, realmUtils);
}
@Override
protected void parseOrderByForPlainSchema(
- final SearchSupport svs,
final OrderBySupport obs,
final OrderBySupport.Item item,
final Sort.Order clause,
@@ -78,53 +93,45 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
// keep track of involvement of non-mandatory schemas in the order by clauses
obs.nonMandatorySchemas = !"true".equals(schema.getMandatoryCondition());
- obs.views.add(svs.field());
-
- item.select = new StringBuilder().append(schema.isUniqueConstraint()
- ? "( SELECT JSON_UNQUOTE(JSON_EXTRACT(usa.attrUniqueValue, '$." + key(schema.getType()) + "')) "
- : "( SELECT usa." + key(schema.getType())).
- append(" FROM ").
- append(svs.field().name()).
- append(" usa WHERE usa.any_id = ").
- append(svs.field().alias()).
- append(".any_id").
- append(" AND usa.plainSchema ='").append(fieldName).append("'").
- append(" LIMIT 1").
- append(") AS ").append(fieldName).toString();
- item.where = "plainSchema = '" + fieldName + '\'';
+ item.select = schema.getKey() + "."
+ + (schema.isUniqueConstraint() ? "uniqueValue" : "valuez")
+ + " AS " + schema.getKey();
+ item.where = StringUtils.EMPTY;
item.orderBy = fieldName + ' ' + clause.getDirection().name();
}
- protected AnySearchNode.Leaf filJSONAttrQuery(
- final SearchSupport.SearchView from,
+ protected RealmSearchNode.Leaf filJSONAttrQuery(
final PlainAttrValue attrValue,
final PlainSchema schema,
final AttrCond cond,
final boolean not,
final List<Object> parameters) {
- String key = key(schema.getType());
-
String value = Optional.ofNullable(attrValue.getDateValue()).
map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).
orElseGet(cond::getExpression);
+ Object typedValue = schema.getType() == AttrSchemaType.Date ? value : attrValue.getValue();
- boolean lower = (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum)
- && (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
+ boolean isString = schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum;
+ boolean lower = isString && (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
+ boolean binary = isString && !lower;
- StringBuilder clause = new StringBuilder("plainSchema=?").append(setParameter(parameters, cond.getSchema())).
+ StringBuilder clause = new StringBuilder(schema.getKey()).
+ append(".schemaz=?").append(setParameter(parameters, cond.getSchema())).
append(" AND ").
append(lower ? "LOWER(" : "").
+ append(binary ? "BINARY " : "").
+ append(schema.getKey()).append('.').
append(schema.isUniqueConstraint()
- ? "JSON_UNQUOTE(JSON_EXTRACT(attrUniqueValue, '$." + key + "'))"
- : key).
+ ? "uniqueValue"
+ : "valuez").
append(lower ? ')' : "");
switch (cond.getType()) {
case LIKE:
case ILIKE:
if (not) {
- clause.append("NOT ");
+ clause.append(" NOT");
}
clause.append(" LIKE ");
break;
@@ -171,10 +178,13 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
}
clause.append(lower ? "LOWER(" : "").
- append('?').append(setParameter(parameters, value)).
+ append('?').append(setParameter(parameters,
+ cond.getType() == AttrCond.Type.LIKE || cond.getType() == AttrCond.Type.ILIKE
+ ? value
+ : typedValue)).
append(lower ? ")" : "");
- return new AnySearchNode.Leaf(from, clause.toString());
+ return new RealmSearchNode.Leaf(clause.toString());
}
@Override
@@ -182,10 +192,8 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
final AttrCond cond,
final boolean not,
final CheckResult<AttrCond> checked,
- final List<Object> parameters,
- final SearchSupport svs) {
+ final List<Object> parameters) {
- // normalize NULL / NOT NULL checks
if (not) {
if (cond.getType() == AttrCond.Type.ISNULL) {
cond.setType(AttrCond.Type.ISNOTNULL);
@@ -196,18 +204,16 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
switch (cond.getType()) {
case ISNOTNULL -> {
- return new AttrCondQuery(true, new AnySearchNode.Leaf(
- svs.field(),
+ return new AttrCondQuery(true, new RealmSearchNode.Leaf(
"JSON_SEARCH("
- + "plainAttrs, 'one', '" + checked.schema().getKey() + "', NULL, '$[*].schema'"
+ + "r.plainAttrs, 'one', '" + checked.schema().getKey() + "', NULL, '$[*].schema'"
+ ") IS NOT NULL"));
}
case ISNULL -> {
- return new AttrCondQuery(true, new AnySearchNode.Leaf(
- svs.field(),
+ return new AttrCondQuery(true, new RealmSearchNode.Leaf(
"JSON_SEARCH("
- + "plainAttrs, 'one', '" + checked.schema().getKey() + "', NULL, '$[*].schema'"
+ + "r.plainAttrs, 'one', '" + checked.schema().getKey() + "', NULL, '$[*].schema'"
+ ") IS NULL"));
}
@@ -221,30 +227,26 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
container.add(checked.value());
}
- return new AttrCondQuery(true, new AnySearchNode.Leaf(
- svs.field(),
+ return new AttrCondQuery(true, new RealmSearchNode.Leaf(
"JSON_CONTAINS("
- + "plainAttrs, '" + POJOHelper.serialize(List.of(container)).replace("'", "''")
+ + "r.plainAttrs, '" + POJOHelper.serialize(List.of(container)).replace("'", "''")
+ "')"));
} else {
- AnySearchNode.Leaf node;
+ RealmSearchNode.Leaf node;
if (not && checked.schema().isMultivalue()) {
- AnySearchNode.Leaf notNode = filJSONAttrQuery(
- svs.field(),
+ RealmSearchNode.Leaf notNode = filJSONAttrQuery(
checked.value(),
checked.schema(),
cond,
false,
parameters);
- node = new AnySearchNode.Leaf(
- notNode.getFrom(),
- "sv.any_id NOT IN ("
- + "SELECT any_id FROM " + notNode.getFrom().name()
- + " WHERE " + notNode.getClause().replace(notNode.getFrom().alias() + ".", "")
+ node = new RealmSearchNode.Leaf(
+ "r.id NOT IN ("
+ + "SELECT r.id FROM " + JPARealm.TABLE + " r, " + from(checked.schema())
+ + " WHERE " + notNode.getClause()
+ ")");
} else {
node = filJSONAttrQuery(
- svs.field(),
checked.value(),
checked.schema(),
cond,
@@ -256,4 +258,24 @@ public class MySQLJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
}
}
}
+
+ @Override
+ protected String buildFrom(final Set<String> plainSchemas, final OrderBySupport obs) {
+ StringBuilder clause = new StringBuilder(super.buildFrom(plainSchemas, obs));
+
+ plainSchemas.forEach(schema -> plainSchemaDAO.findById(schema).
+ ifPresent(pschema -> clause.append(",").append(from(pschema))));
+
+ if (obs != null) {
+ obs.items.forEach(item -> {
+ String schema = StringUtils.substringBefore(item.orderBy, ' ');
+ if (StringUtils.isNotBlank(schema) && !plainSchemas.contains(schema)) {
+ plainSchemaDAO.findById(schema).ifPresent(
+ pschema -> clause.append(" LEFT OUTER JOIN ").append(from(pschema)).append(" ON 1=1"));
+ }
+ });
+ }
+
+ return clause.toString();
+ }
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPAAnySearchDAO.java
index 6ee2ebf412..bf2dedc071 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPAAnySearchDAO.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
@@ -67,7 +66,6 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
final EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
super(
@@ -79,7 +77,6 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPARealmSearchDAO.java
similarity index 51%
copy from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPAAnySearchDAO.java
copy to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPARealmSearchDAO.java
index 6ee2ebf412..7ae1fd9f42 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/OracleJPARealmSearchDAO.java
@@ -19,38 +19,27 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.time.format.DateTimeFormatter;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.types.AttrSchemaType;
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.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
-import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
-import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
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.utils.RealmUtils;
+import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
import org.springframework.data.domain.Sort;
-public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
+public class OracleJPARealmSearchDAO extends AbstractJPARealmSearchDAO {
- /**
- *
- * @param schema
- * @return JSON_TABLE(plainAttrs, '$[*]?(@.schema == "fullname").uniqueValue' \
- * COLUMNS uniqueValue PATH '$.stringValue') AS fullname
- * or JSON_TABLE(plainAttrs, '$[*]?(@.schema == "loginDate").values[*]' \
- * COLUMNS valuez PATH '$.dateValue') AS loginDate
- */
- public static String from(final PlainSchema schema) {
+ protected static String from(final PlainSchema schema) {
return new StringBuilder("JSON_TABLE(plainAttrs, '$[*]?(@.schema == \"").append(schema.getKey()).append("\").").
append(schema.isUniqueConstraint() ? "uniqueValue" : "values[*]").
append("' COLUMNS ").append(schema.isUniqueConstraint() ? "uniqueValue" : "valuez").
@@ -58,44 +47,20 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
toString();
}
- public OracleJPAAnySearchDAO(
- final RealmSearchDAO realmSearchDAO,
+ public OracleJPARealmSearchDAO(
+ final EntityManager entityManager,
+ final PlainSchemaDAO plainSchemaDAO,
final UserDAO userDAO,
final GroupDAO groupDAO,
- final AnyObjectDAO anyObjectDAO,
- final PlainSchemaDAO schemaDAO,
final EntityFactory entityFactory,
- final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
- final EntityManager entityManager) {
+ final RealmUtils realmUtils) {
- super(
- realmSearchDAO,
- userDAO,
- groupDAO,
- anyObjectDAO,
- schemaDAO,
- entityFactory,
- anyUtilsFactory,
- validator,
- entityManagerFactory,
- entityManager);
- }
-
- @Override
- protected SearchSupport.SearchView defaultSV(final SearchSupport svs) {
- return svs.table();
- }
-
- @Override
- protected String anyId(final SearchSupport.SearchView sv) {
- return sv.alias() + ".id";
+ super(entityManager, plainSchemaDAO, userDAO, groupDAO, entityFactory, validator, realmUtils);
}
@Override
protected void parseOrderByForPlainSchema(
- final SearchSupport svs,
final OrderBySupport obs,
final OrderBySupport.Item item,
final Sort.Order clause,
@@ -105,8 +70,6 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
// keep track of involvement of non-mandatory schemas in the order by clauses
obs.nonMandatorySchemas = !"true".equals(schema.getMandatoryCondition());
- obs.views.add(svs.table());
-
item.select = schema.getKey() + "."
+ (schema.isUniqueConstraint() ? "uniqueValue" : "valuez")
+ " AS " + schema.getKey();
@@ -114,20 +77,7 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
item.orderBy = fieldName + ' ' + clause.getDirection().name();
}
- @Override
- protected void parseOrderByForField(
- final SearchSupport svs,
- final OrderBySupport.Item item,
- final String fieldName,
- final Sort.Order clause) {
-
- item.select = svs.table().alias() + '.' + fieldName;
- item.where = StringUtils.EMPTY;
- item.orderBy = svs.table().alias() + '.' + fieldName + ' ' + clause.getDirection().name();
- }
-
- protected AnySearchNode.Leaf filJSONAttrQuery(
- final SearchSupport.SearchView from,
+ protected RealmSearchNode.Leaf filJSONAttrQuery(
final PlainAttrValue attrValue,
final PlainSchema schema,
final AttrCond cond,
@@ -137,6 +87,7 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
String value = Optional.ofNullable(attrValue.getDateValue()).
map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).
orElseGet(cond::getExpression);
+ Object typedValue = schema.getType() == AttrSchemaType.Date ? value : attrValue.getValue();
boolean lower = (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum)
&& (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
@@ -149,61 +100,45 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
case LIKE:
case ILIKE:
if (not) {
- clause.append("NOT ");
+ clause.append(" NOT");
}
- clause.append(" LIKE ");
+ clause.append(" LIKE ").
+ append(lower ? "LOWER(" : "").
+ append('?').append(setParameter(parameters, value)).
+ append(lower ? ')' : "").append(" ESCAPE '\\'");
break;
case GE:
- if (not) {
- clause.append('<');
- } else {
- clause.append(">=");
- }
+ clause.append(not ? "<" : ">=").
+ append('?').append(setParameter(parameters, typedValue));
break;
case GT:
- if (not) {
- clause.append("<=");
- } else {
- clause.append('>');
- }
+ clause.append(not ? "<=" : ">").
+ append('?').append(setParameter(parameters, typedValue));
break;
case LE:
- if (not) {
- clause.append('>');
- } else {
- clause.append("<=");
- }
+ clause.append(not ? ">" : "<=").
+ append('?').append(setParameter(parameters, typedValue));
break;
case LT:
- if (not) {
- clause.append(">=");
- } else {
- clause.append('<');
- }
+ clause.append(not ? ">=" : "<").
+ append('?').append(setParameter(parameters, typedValue));
break;
- case EQ:
case IEQ:
+ case EQ:
default:
- if (not) {
- clause.append('!');
- }
- clause.append('=');
+ clause.append(not ? " != " : " = ").
+ append(lower ? "LOWER(" : "").
+ append('?').append(setParameter(parameters, typedValue)).
+ append(lower ? ")" : "");
+ break;
}
- clause.append(lower ? "LOWER(" : "").
- append('?').append(setParameter(parameters, value)).
- append(lower ? ")" : "");
-
- // workaround for Oracle DB adding explicit escaping string, to search for literal _ (underscore)
- if (cond.getType() == AttrCond.Type.ILIKE || cond.getType() == AttrCond.Type.LIKE) {
- clause.append(" ESCAPE '\\'");
- }
- return new AnySearchNode.Leaf(from, clause.toString());
+ return new RealmSearchNode.Leaf(clause.toString());
}
@Override
@@ -211,10 +146,8 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
final AttrCond cond,
final boolean not,
final CheckResult<AttrCond> checked,
- final List<Object> parameters,
- final SearchSupport svs) {
+ final List<Object> parameters) {
- // normalize NULL / NOT NULL checks
if (not) {
if (cond.getType() == AttrCond.Type.ISNULL) {
cond.setType(AttrCond.Type.ISNOTNULL);
@@ -225,67 +158,40 @@ public class OracleJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
switch (cond.getType()) {
case ISNOTNULL -> {
- return new AttrCondQuery(false, new AnySearchNode.Leaf(
- svs.table(),
- "JSON_EXISTS(plainAttrs, '$[*]?(@.schema == \"" + checked.schema().getKey() + "\")')"));
+ return new AttrCondQuery(false, new RealmSearchNode.Leaf(
+ "JSON_EXISTS(r.plainAttrs, '$[*]?(@.schema == \""
+ + checked.schema().getKey() + "\")')"));
}
case ISNULL -> {
- return new AttrCondQuery(false, new AnySearchNode.Leaf(
- svs.table(),
- "NOT JSON_EXISTS(plainAttrs, '$[*]?(@.schema == \"" + checked.schema().getKey() + "\")')"));
+ return new AttrCondQuery(false, new RealmSearchNode.Leaf(
+ "NOT JSON_EXISTS(r.plainAttrs, '$[*]?(@.schema == \""
+ + checked.schema().getKey() + "\")')"));
}
default -> {
- AnySearchNode.Leaf node;
+ RealmSearchNode.Leaf node;
if (not && checked.schema().isMultivalue()) {
- AnySearchNode.Leaf notNode = filJSONAttrQuery(
- svs.table(),
- checked.value(),
- checked.schema(),
- cond,
- false,
- parameters);
- node = new AnySearchNode.Leaf(
- notNode.getFrom(),
+ RealmSearchNode.Leaf notNode = filJSONAttrQuery(
+ checked.value(), checked.schema(), cond, false, parameters);
+ node = new RealmSearchNode.Leaf(
"id NOT IN ("
- + "SELECT id FROM " + notNode.getFrom().name() + "," + from(checked.schema())
- + " WHERE " + notNode.getClause().replace(notNode.getFrom().alias() + ".", "")
+ + "SELECT id FROM " + JPARealm.TABLE + " e, " + from(checked.schema())
+ + " WHERE " + notNode.getClause()
+ ")");
return new AttrCondQuery(false, node);
} else {
- node = filJSONAttrQuery(
- svs.table(),
- checked.value(),
- checked.schema(),
- cond,
- not,
- parameters);
+ node = filJSONAttrQuery(checked.value(), checked.schema(), cond, not, parameters);
}
return new AttrCondQuery(true, node);
}
- }
- }
- @Override
- protected void visitNode(
- final AnySearchNode node,
- final Map<SearchSupport.SearchView, Boolean> counters,
- final Set<SearchSupport.SearchView> from,
- final List<String> where,
- final SearchSupport svs) {
-
- counters.clear();
- super.visitNode(node, counters, from, where, svs);
+ }
}
@Override
- protected String buildFrom(
- final Set<SearchSupport.SearchView> from,
- final Set<String> plainSchemas,
- final OrderBySupport obs) {
-
- StringBuilder clause = new StringBuilder(super.buildFrom(from, plainSchemas, obs));
+ protected String buildFrom(final Set<String> plainSchemas, final OrderBySupport obs) {
+ StringBuilder clause = new StringBuilder(super.buildFrom(plainSchemas, obs));
plainSchemas.forEach(schema -> plainSchemaDAO.findById(schema).
ifPresent(pschema -> clause.append(",").append(from(pschema))));
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAAnySearchDAO.java
index 01a6a25d79..c1dae44bb8 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAAnySearchDAO.java
@@ -19,7 +19,6 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
@@ -68,7 +67,6 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
final EntityFactory entityFactory,
final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
final EntityManager entityManager) {
super(
@@ -80,7 +78,6 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
entityFactory,
anyUtilsFactory,
validator,
- entityManagerFactory,
entityManager);
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPARealmSearchDAO.java
similarity index 59%
copy from core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAAnySearchDAO.java
copy to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPARealmSearchDAO.java
index 01a6a25d79..7bac97a495 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPARealmSearchDAO.java
@@ -19,29 +19,25 @@
package org.apache.syncope.core.persistence.jpa.dao;
import jakarta.persistence.EntityManager;
-import jakarta.persistence.EntityManagerFactory;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.types.AttrSchemaType;
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.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
-import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
-import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
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.utils.RealmUtils;
import org.springframework.data.domain.Sort;
-public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
+public class PGJPARealmSearchDAO extends AbstractJPARealmSearchDAO {
protected static final String REGEX_CHARS = "!$()*+.:<=>?[\\]^{|}-";
@@ -54,49 +50,23 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
}
protected static String escapeIfString(final String value, final boolean isStr) {
- return isStr
- ? new StringBuilder().append('"').append(value.replace("'", "''")).append('"').toString()
- : value;
+ return isStr ? '"' + value.replace("'", "''") + '"' : value;
}
- public PGJPAAnySearchDAO(
- final RealmSearchDAO realmSearchDAO,
+ public PGJPARealmSearchDAO(
+ final EntityManager entityManager,
+ final PlainSchemaDAO plainSchemaDAO,
final UserDAO userDAO,
final GroupDAO groupDAO,
- final AnyObjectDAO anyObjectDAO,
- final PlainSchemaDAO schemaDAO,
final EntityFactory entityFactory,
- final AnyUtilsFactory anyUtilsFactory,
final PlainAttrValidationManager validator,
- final EntityManagerFactory entityManagerFactory,
- final EntityManager entityManager) {
-
- super(
- realmSearchDAO,
- userDAO,
- groupDAO,
- anyObjectDAO,
- schemaDAO,
- entityFactory,
- anyUtilsFactory,
- validator,
- entityManagerFactory,
- entityManager);
- }
+ final RealmUtils realmUtils) {
- @Override
- protected SearchSupport.SearchView defaultSV(final SearchSupport svs) {
- return svs.table();
- }
-
- @Override
- protected String anyId(final SearchSupport.SearchView sv) {
- return sv.alias() + ".id";
+ super(entityManager, plainSchemaDAO, userDAO, groupDAO, entityFactory, validator, realmUtils);
}
@Override
protected void parseOrderByForPlainSchema(
- final SearchSupport svs,
final OrderBySupport obs,
final OrderBySupport.Item item,
final Sort.Order clause,
@@ -106,33 +76,19 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
// keep track of involvement of non-mandatory schemas in the order by clauses
obs.nonMandatorySchemas = !"true".equals(schema.getMandatoryCondition());
- obs.views.add(svs.table());
-
item.select = fieldName + " -> 0 AS " + fieldName;
item.where = StringUtils.EMPTY;
item.orderBy = fieldName + ' ' + clause.getDirection().name();
}
- @Override
- protected void parseOrderByForField(
- final SearchSupport svs,
- final OrderBySupport.Item item,
- final String fieldName,
- final Sort.Order clause) {
-
- item.select = svs.table().alias() + '.' + fieldName;
- item.where = StringUtils.EMPTY;
- item.orderBy = svs.table().alias() + '.' + fieldName + ' ' + clause.getDirection().name();
- }
-
- protected AnySearchNode.Leaf filJSONAttrQuery(
- final SearchSupport.SearchView from,
+ protected RealmSearchNode.Leaf filJSONAttrQuery(
final PlainAttrValue attrValue,
final PlainSchema schema,
final AttrCond cond,
final boolean not) {
String key = key(schema.getType());
+ String valuesPath = schema.isUniqueConstraint() ? "uniqueValue" : "values[*]";
String value = Optional.ofNullable(attrValue.getDateValue()).
map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).
@@ -141,30 +97,25 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
boolean isStr = true;
boolean lower = false;
if (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) {
- lower = (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
+ lower = cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE;
} else if (schema.getType() != AttrSchemaType.Date) {
- lower = false;
try {
switch (schema.getType()) {
case Long ->
Long.valueOf(value);
-
case Double ->
Double.valueOf(value);
-
case Boolean -> {
if (!("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
throw new IllegalArgumentException();
}
}
-
default -> {
}
}
-
isStr = false;
} catch (Exception nfe) {
- // ignore
+ // ignore — treat as string
}
}
@@ -172,57 +123,63 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
switch (cond.getType()) {
case ILIKE:
case LIKE:
- // jsonb_path_exists(Nome, '$[*] ? (@.stringValue like_regex "EL.*" flag "i")')
if (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) {
- clause.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
- append("(@.").append(key).append(" like_regex \"").
- append(escapeForLikeRegex(value).replace("%", ".*")).
- append("\"").
+ clause.append("jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \"").
+ append(schema.getKey()).append("\").").append(valuesPath).
+ append(" ? (@.").append(key).append(" like_regex \"").
+ append(escapeForLikeRegex(value).replace("%", ".*")).append("\"").
append(lower ? " flag \"i\"" : "").append(")')");
} else {
LOG.error("LIKE is only compatible with string or enum schemas");
- clause.append(' ').append(ALWAYS_FALSE_CLAUSE);
+ clause.append(ALWAYS_FALSE_CLAUSE);
}
break;
case IEQ:
case EQ:
default:
- clause.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
- append("(@.").append(key);
-
+ clause.append("jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \"").
+ append(schema.getKey()).append("\").").append(valuesPath).
+ append(" ? (");
if (StringUtils.containsAny(value, REGEX_CHARS) || lower) {
- clause.append(" like_regex \"^").
- append(escapeForLikeRegex(value).replace("'", "''")).
- append("$\"");
+ clause.append("@.").append(key).append(" like_regex \"^").
+ append(escapeForLikeRegex(value).replace("'", "''")).append("$\"");
+ } else if (isStr) {
+ clause.append("@.").append(key).append(" == ").append(escapeIfString(value, true));
} else {
- clause.append(" == ").append(escapeIfString(value, isStr));
+ // Some datasets can store scalar values as JSON strings; accept both representations.
+ clause.append("@.").append(key).append(" == ").append(escapeIfString(value, false)).
+ append(" || @.").append(key).append(" == ").append(escapeIfString(value, true)).
+ append(" || @.stringValue == ").append(escapeIfString(value, true));
}
-
clause.append(lower ? " flag \"i\"" : "").append(")')");
break;
case GE:
- clause.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
- append("(@.").append(key).append(" >= ").
+ clause.append("jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \"").
+ append(schema.getKey()).append("\").").append(valuesPath).
+ append(" ? (@.").append(key).append(" >= ").
append(escapeIfString(value, isStr)).append(")')");
break;
case GT:
- clause.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
- append("(@.").append(key).append(" > ").
+ clause.append("jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \"").
+ append(schema.getKey()).append("\").").append(valuesPath).
+ append(" ? (@.").append(key).append(" > ").
append(escapeIfString(value, isStr)).append(")')");
break;
case LE:
- clause.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
- append("(@.").append(key).append(" <= ").
+ clause.append("jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \"").
+ append(schema.getKey()).append("\").").append(valuesPath).
+ append(" ? (@.").append(key).append(" <= ").
append(escapeIfString(value, isStr)).append(")')");
break;
case LT:
- clause.append("jsonb_path_exists(").append(schema.getKey()).append(", '$[*] ? ").
- append("(@.").append(key).append(" < ").
+ clause.append("jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \"").
+ append(schema.getKey()).append("\").").append(valuesPath).
+ append(" ? (@.").append(key).append(" < ").
append(escapeIfString(value, isStr)).append(")')");
break;
}
@@ -231,7 +188,7 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
clause.insert(0, "NOT ");
}
- return new AnySearchNode.Leaf(from, clause.toString());
+ return new RealmSearchNode.Leaf(clause.toString());
}
@Override
@@ -239,10 +196,8 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
final AttrCond cond,
final boolean not,
final CheckResult<AttrCond> checked,
- final List<Object> parameters,
- final SearchSupport svs) {
+ final List<Object> parameters) {
- // normalize NULL / NOT NULL checks
if (not) {
if (cond.getType() == AttrCond.Type.ISNULL) {
cond.setType(AttrCond.Type.ISNOTNULL);
@@ -251,46 +206,40 @@ public class PGJPAAnySearchDAO extends AbstractJPAAnySearchDAO {
}
}
- return switch (cond.getType()) {
- case ISNOTNULL ->
- new AttrCondQuery(true, new AnySearchNode.Leaf(
- svs.table(),
- "jsonb_path_exists(" + checked.schema().getKey() + ",'$[*]')"));
-
- case ISNULL ->
- new AttrCondQuery(true, new AnySearchNode.Leaf(
- svs.table(),
- "NOT jsonb_path_exists(" + checked.schema().getKey() + ",'$[*]')"));
-
- default ->
- new AttrCondQuery(true, filJSONAttrQuery(
- svs.table(),
- checked.value(),
- checked.schema(),
- cond,
- not));
- };
- }
+ switch (cond.getType()) {
+ case ISNOTNULL -> {
+ return new AttrCondQuery(true, new RealmSearchNode.Leaf(
+ "jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \""
+ + checked.schema().getKey() + "\")')"));
+ }
- @Override
- protected void visitNode(
- final AnySearchNode node,
- final Map<SearchSupport.SearchView, Boolean> counters,
- final Set<SearchSupport.SearchView> from,
- final List<String> where,
- final SearchSupport svs) {
-
- counters.clear();
- super.visitNode(node, counters, from, where, svs);
+ case ISNULL -> {
+ return new AttrCondQuery(true, new RealmSearchNode.Leaf(
+ "NOT jsonb_path_exists(r.plainAttrs, '$[*] ? (@.schema == \""
+ + checked.schema().getKey() + "\")')"));
+ }
+
+ default -> {
+ RealmSearchNode.Leaf node;
+ if (not && checked.schema().isMultivalue()) {
+ // negate multivalue by wrapping the positive expression in NOT
+ RealmSearchNode.Leaf notNode = filJSONAttrQuery(
+ checked.value(), checked.schema(), cond, false);
+ node = new RealmSearchNode.Leaf("NOT " + notNode.getClause());
+ } else {
+ node = filJSONAttrQuery(checked.value(), checked.schema(), cond, not);
+ }
+ return new AttrCondQuery(true, node);
+ }
+ }
}
@Override
protected String buildFrom(
- final Set<SearchSupport.SearchView> from,
final Set<String> plainSchemas,
final OrderBySupport obs) {
- StringBuilder clause = new StringBuilder(super.buildFrom(from, plainSchemas, obs));
+ StringBuilder clause = new StringBuilder(super.buildFrom(plainSchemas, obs));
Set<String> schemas = new HashSet<>(plainSchemas);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/RealmSearchNode.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/RealmSearchNode.java
new file mode 100644
index 0000000000..b9b5c08b6b
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/RealmSearchNode.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.dao;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+public class RealmSearchNode {
+
+ public enum Type {
+ AND,
+ OR,
+ LEAF
+ }
+
+ public static class Leaf extends RealmSearchNode {
+
+ private final String clause;
+
+ protected Leaf(final String clause) {
+ super(Type.LEAF);
+ this.clause = clause;
+ }
+
+ public String getClause() {
+ return clause;
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().
+ appendSuper(super.hashCode()).
+ append(clause).
+ build();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Leaf other = (Leaf) obj;
+
+ return new EqualsBuilder().
+ appendSuper(super.equals(obj)).
+ append(clause, other.clause).
+ build();
+ }
+
+ @Override
+ public String toString() {
+ return "LeafNode{clause=" + clause + '}';
+ }
+ }
+
+ private final Type type;
+
+ private final List<RealmSearchNode> children = new ArrayList<>();
+
+ public RealmSearchNode(final Type type) {
+ this.type = type;
+ }
+
+ protected Type getType() {
+ return type;
+ }
+
+ protected boolean add(final RealmSearchNode child) {
+ if (type == Type.LEAF) {
+ throw new IllegalArgumentException("Cannot add children to a leaf node");
+ }
+ return children.add(child);
+ }
+
+ protected List<RealmSearchNode> getChildren() {
+ return children;
+ }
+
+ protected Optional<Leaf> asLeaf() {
+ return type == Type.LEAF
+ ? Optional.of((Leaf) this)
+ : Optional.empty();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().
+ append(type).
+ append(children).
+ build();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final RealmSearchNode other = (RealmSearchNode) obj;
+
+ return new EqualsBuilder().
+ append(type, other.type).
+ append(children, other.children).
+ build();
+ }
+
+ @Override
+ public String toString() {
+ return "Node{" + "type=" + type + ", children=" + children + '}';
+ }
+}
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 e08a2f1578..a7eb723478 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
@@ -22,7 +22,6 @@ import jakarta.persistence.EntityManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.OffsetDateTime;
-import java.util.List;
import java.util.Optional;
import org.apache.syncope.core.persistence.api.dao.AnyChecker;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -30,7 +29,6 @@ import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.AnyUtils;
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.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
@@ -48,8 +46,6 @@ public abstract class AbstractAnyRepoExt<A extends Any> implements AnyRepoExt<A>
protected final AnyChecker anyChecker;
- protected final AnyFinder anyFinder;
-
protected final AnyUtils anyUtils;
protected final String table;
@@ -57,12 +53,10 @@ public abstract class AbstractAnyRepoExt<A extends Any> implements AnyRepoExt<A>
protected AbstractAnyRepoExt(
final EntityManager entityManager,
final AnyChecker anyChecker,
- final AnyFinder anyFinder,
final AnyUtils anyUtils) {
this.entityManager = entityManager;
this.anyChecker = anyChecker;
- this.anyFinder = anyFinder;
this.anyUtils = anyUtils;
switch (anyUtils.anyTypeKind()) {
case ANY_OBJECT:
@@ -133,11 +127,6 @@ public abstract class AbstractAnyRepoExt<A extends Any> implements AnyRepoExt<A>
return any;
}
- @Override
- public List<A> findByDerAttrValue(final String expression, final String value, final boolean ignoreCaseMatch) {
- return anyFinder.findByDerAttrValue(anyUtils.anyTypeKind(), expression, value, ignoreCaseMatch);
- }
-
@Override
public void deleteRelationship(final Relationship<? extends A, AnyObject> relationship) {
entityManager.remove(relationship);
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 39f771f323..4d80469dd7 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
@@ -44,7 +44,6 @@ 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.URelationship;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
@@ -63,13 +62,11 @@ public class AnyObjectRepoExtImpl extends AbstractAnyRepoExt<AnyObject> implemen
final UserDAO userDAO,
final GroupDAO groupDAO,
final EntityManager entityManager,
- final AnyChecker anyChecker,
- final AnyFinder anyFinder) {
+ final AnyChecker anyChecker) {
super(
entityManager,
anyChecker,
- anyFinder,
anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT));
this.userDAO = userDAO;
this.groupDAO = groupDAO;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java
index 9ae2f80b5e..cb30b760f5 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/AnyRepoExt.java
@@ -20,7 +20,6 @@ package org.apache.syncope.core.persistence.jpa.dao.repo;
import java.time.OffsetDateTime;
import java.util.Collection;
-import java.util.List;
import java.util.Optional;
import org.apache.syncope.core.persistence.api.entity.Any;
import org.apache.syncope.core.persistence.api.entity.Relationship;
@@ -32,8 +31,6 @@ public interface AnyRepoExt<A extends Any> {
A authFind(String key);
- List<A> findByDerAttrValue(String expression, String value, boolean ignoreCaseMatch);
-
Collection<String> findAllResourceKeys(String key);
void deleteRelationship(Relationship<? extends A, AnyObject> relationship);
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 8d074434bb..146a2ed96c 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
@@ -43,7 +43,6 @@ import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension;
import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
@@ -75,13 +74,11 @@ public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group> implements Group
final UserDAO userDAO,
final AnyObjectDAO anyObjectDAO,
final EntityManager entityManager,
- final AnyChecker anyChecker,
- final AnyFinder anyFinder) {
+ final AnyChecker anyChecker) {
super(
entityManager,
anyChecker,
- anyFinder,
anyUtilsFactory.getInstance(AnyTypeKind.GROUP));
this.publisher = publisher;
this.realmDAO = realmDAO;
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 51e350f750..cfdfe0d0cf 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
@@ -47,7 +47,6 @@ import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
@@ -81,13 +80,11 @@ public class UserRepoExtImpl extends AbstractAnyRepoExt<User> implements UserRep
final FIQLQueryDAO fiqlQueryDAO,
final SecurityProperties securityProperties,
final EntityManager entityManager,
- final AnyChecker anyChecker,
- final AnyFinder anyFinder) {
+ final AnyChecker anyChecker) {
super(
entityManager,
anyChecker,
- anyFinder,
anyUtilsFactory.getInstance(AnyTypeKind.USER));
this.roleDAO = roleDAO;
this.accessTokenDAO = accessTokenDAO;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASRARoute.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASRARoute.java
index 00ee143dea..2a92e79213 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASRARoute.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASRARoute.java
@@ -157,7 +157,7 @@ public class JPASRARoute extends AbstractGeneratedKeyEntity implements SRARoute
public List<SRARouteFilter> getFilters() {
return Optional.ofNullable(filters).
map(f -> List.of(POJOHelper.deserialize(f, SRARouteFilter[].class))).
- orElseGet(List::of);
+ orElseGet(List::of);
}
@Override
@@ -169,7 +169,7 @@ public class JPASRARoute extends AbstractGeneratedKeyEntity implements SRARoute
public List<SRARoutePredicate> getPredicates() {
return Optional.ofNullable(predicates).
map(f -> List.of(POJOHelper.deserialize(f, SRARoutePredicate[].class))).
- orElseGet(List::of);
+ orElseGet(List::of);
}
@Override
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
index 03c1ce7f3a..39cf78fb16 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnySearchTest.java
@@ -39,6 +39,7 @@ import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationMana
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.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
@@ -107,6 +108,9 @@ public class AnySearchTest extends AbstractTest {
@Autowired
private PlainSchemaDAO plainSchemaDAO;
+ @Autowired
+ private DerSchemaDAO derSchemaDAO;
+
@Autowired
private PlainAttrValidationManager validator;
@@ -730,13 +734,13 @@ public class AnySearchTest extends AbstractTest {
assertEquals(
1,
searchDAO.count(
- realmDAO.getRoot(), true, authRealms, groupDAO.getAllMatchingCond(), AnyTypeKind.GROUP));
+ realmDAO.getRoot(), true, authRealms, searchDAO.getAllMatchingCond(), AnyTypeKind.GROUP));
List<Group> groups = searchDAO.search(
realmDAO.getRoot(),
true,
authRealms,
- groupDAO.getAllMatchingCond(),
+ searchDAO.getAllMatchingCond(),
PageRequest.of(0, 10),
AnyTypeKind.GROUP);
assertEquals(1, groups.size());
@@ -763,6 +767,35 @@ public class AnySearchTest extends AbstractTest {
assertEquals(5, users.size());
}
+ @Test
+ public void findByDerAttrValue() {
+ List<User> list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(), "Vivaldi, Antonio", false, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+
+ list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", false, AnyTypeKind.USER);
+ assertEquals(0, list.size());
+
+ list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", true, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void findByInvalidDerAttrValue() {
+ assertTrue(searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(),
+ "Antonio, Maria, Rossi", false, AnyTypeKind.USER).isEmpty());
+ }
+
+ @Test
+ public void findByInvalidDerAttrExpression() {
+ assertThrows(IllegalArgumentException.class, () -> searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("noschema").orElseThrow().getExpression(),
+ "Antonio, Maria", false, AnyTypeKind.USER).isEmpty());
+ }
+
@Test
public void issue202() {
ResourceCond ws2 = new ResourceCond();
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
index d1531ffd8b..ab83a95e8c 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/MultitenancyTest.java
@@ -35,7 +35,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Tag;
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;
@@ -74,10 +73,10 @@ public class MultitenancyTest extends AbstractTest {
public void readRealm() {
assertEquals(
1,
- realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, Pageable.unpaged()).size());
+ realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null).size());
assertEquals(
realmDAO.getRoot(),
- realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, Pageable.unpaged()).getFirst());
+ realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null).getFirst());
}
@Test
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java
index 5dddbdb6cb..55453d5d9a 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/RealmTest.java
@@ -98,14 +98,14 @@ public class RealmTest extends AbstractTest {
@Test
public void findAll() {
- List<Realm> list = realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, Pageable.unpaged());
+ List<Realm> list = realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null);
assertNotNull(list);
assertFalse(list.isEmpty());
list.forEach(Assertions::assertNotNull);
assertEquals(4, realmDAO.findAll(Pageable.unpaged()).stream().count());
- list = realmSearchDAO.findDescendants(Set.of("/even", "/odd"), null, Pageable.unpaged());
+ list = realmSearchDAO.search(Set.of("/even", "/odd"), realmSearchDAO.getAllMatchingCond(), Pageable.unpaged());
assertEquals(3, list.size());
assertNotNull(list.stream().filter(realm -> "even".equals(realm.getName())).findFirst().orElseThrow());
assertNotNull(list.stream().filter(realm -> "two".equals(realm.getName())).findFirst().orElseThrow());
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
index 37477b2100..29dc05d80d 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/UserTest.java
@@ -30,7 +30,6 @@ import java.time.OffsetDateTime;
import java.util.List;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.core.persistence.api.EncryptorManager;
-import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
@@ -61,9 +60,6 @@ public class UserTest extends AbstractTest {
@Autowired
private ExternalResourceDAO resourceDAO;
- @Autowired
- private DerSchemaDAO derSchemaDAO;
-
@Autowired
private SecurityQuestionDAO securityQuestionDAO;
@@ -109,33 +105,6 @@ public class UserTest extends AbstractTest {
assertEquals(5, count);
}
- @Test
- public void findByDerAttrValue() {
- List<User> list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "Vivaldi, Antonio", false);
- assertEquals(1, list.size());
-
- list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", false);
- assertEquals(0, list.size());
-
- list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", true);
- assertEquals(1, list.size());
- }
-
- @Test
- public void findByInvalidDerAttrValue() {
- assertTrue(userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "Antonio, Maria, Rossi", false).isEmpty());
- }
-
- @Test
- public void findByInvalidDerAttrExpression() {
- assertThrows(IllegalArgumentException.class, () -> userDAO.findByDerAttrValue(
- derSchemaDAO.findById("noschema").orElseThrow().getExpression(), "Antonio, Maria", false).isEmpty());
- }
-
@Test
public void findByKey() {
assertTrue(userDAO.findById("1417acbe-cbf6-4277-9372-e75e04f97000").isPresent());
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 cedcdaeff4..c87034b9da 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
@@ -33,12 +33,14 @@ import org.apache.syncope.common.lib.types.ClientExceptionType;
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.DerSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
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.AttrCond;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.DerSchema;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
@@ -71,6 +73,9 @@ public class AnySearchTest extends AbstractTest {
@Autowired
private RealmDAO realmDAO;
+ @Autowired
+ private DerSchemaDAO derSchemaDAO;
+
@Autowired
private PlainAttrValidationManager validator;
@@ -163,6 +168,43 @@ public class AnySearchTest extends AbstractTest {
assertEquals("c9b2dec2-00a7-4855-97c0-d854842b4b24", users.getFirst().getKey());
}
+ @Test
+ public void issueSYNCOPE800() {
+ // create derived attribute (literal as prefix)
+ DerSchema prefix = entityFactory.newEntity(DerSchema.class);
+ prefix.setKey("kprefix");
+ prefix.setExpression("'k' + firstname");
+
+ derSchemaDAO.save(prefix);
+ entityManager.flush();
+
+ // create derived attribute (literal as suffix)
+ DerSchema suffix = entityFactory.newEntity(DerSchema.class);
+ suffix.setKey("ksuffix");
+ suffix.setExpression("firstname + 'k'");
+
+ derSchemaDAO.save(suffix);
+ entityManager.flush();
+
+ // add derived attributes to user
+ User owner = userDAO.findByUsername("vivaldi").orElseThrow();
+
+ String firstname = owner.getPlainAttr("firstname").get().getValuesAsStrings().getFirst();
+ assertNotNull(firstname);
+
+ // search by ksuffix derived attribute
+ List<User> list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("ksuffix").orElseThrow().getExpression(),
+ firstname + 'k', false, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+
+ // search by kprefix derived attribute
+ list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("kprefix").orElseThrow().getExpression(),
+ 'k' + firstname, false, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+ }
+
@Test
public void issueSYNCOPE1417() {
AnyCond usernameLeafCond = new AnyCond(AnyCond.Type.EQ);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RealmTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RealmTest.java
index ad9bb45f72..273846cc13 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RealmTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/RealmTest.java
@@ -18,15 +18,23 @@
*/
package org.apache.syncope.core.persistence.jpa.outer;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
import org.apache.syncope.core.persistence.api.dao.AnyTypeClassDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
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.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.Role;
@@ -34,6 +42,8 @@ import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.jpa.AbstractTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@@ -105,4 +115,124 @@ public class RealmTest extends AbstractTest {
role = roleDAO.findById("User reviewer").orElseThrow();
assertEquals(beforeSize - 1, role.getRealms().size());
}
+
+ @Test
+ public void search() {
+ Realm two = realmSearchDAO.findByFullPath("/even/two").orElseThrow();
+ two.add(anyTypeClassDAO.findById("other").orElseThrow());
+ two = realmDAO.save(two);
+ entityManager.flush();
+
+ two = realmDAO.findById(two.getKey()).orElseThrow();
+ PlainAttr aLong = new PlainAttr();
+ aLong.setSchema("aLong");
+ aLong.add(validator, "42");
+ two.add(aLong);
+ two = realmDAO.save(two);
+ entityManager.flush();
+
+ Realm odd = realmSearchDAO.findByFullPath("/odd").orElseThrow();
+ odd.add(anyTypeClassDAO.findById("other").orElseThrow());
+ odd = realmDAO.save(odd);
+ entityManager.flush();
+
+ odd = realmDAO.findById(odd.getKey()).orElseThrow();
+ PlainAttr oddLong = new PlainAttr();
+ oddLong.setSchema("aLong");
+ oddLong.add(validator, "99");
+ odd.add(oddLong);
+ realmDAO.save(odd);
+ entityManager.flush();
+
+ two = realmDAO.findById(two.getKey()).orElseThrow();
+ assertEquals(anyTypeClassDAO.findById("other").orElseThrow(), two.getAnyTypeClasses().iterator().next());
+ assertEquals(1, two.getPlainAttrs().size());
+ assertEquals(42, two.getPlainAttr("aLong").orElseThrow().getValues().getFirst().getLongValue());
+
+ odd = realmDAO.findById(odd.getKey()).orElseThrow();
+ assertEquals(anyTypeClassDAO.findById("other").orElseThrow(), odd.getAnyTypeClasses().iterator().next());
+ assertEquals(1, odd.getPlainAttrs().size());
+ assertEquals(99, odd.getPlainAttr("aLong").orElseThrow().getValues().getFirst().getLongValue());
+
+ AnyCond name = new AnyCond(AttrCond.Type.EQ);
+ name.setSchema("name");
+ name.setExpression("two");
+
+ List<Realm> result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(name),
+ Pageable.unpaged());
+ assertEquals(1, result.size());
+ assertEquals("two", result.getFirst().getName());
+
+ AttrCond attrEq = new AttrCond(AttrCond.Type.EQ);
+ attrEq.setSchema("aLong");
+ attrEq.setExpression("42");
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrEq),
+ Pageable.unpaged());
+ assertEquals(1, result.size());
+ assertEquals("two", result.getFirst().getName());
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.and(SearchCond.of(name), SearchCond.of(attrEq)),
+ Pageable.unpaged());
+ assertEquals(1, result.size());
+ assertEquals("two", result.getFirst().getName());
+
+ AttrCond attrEq2 = new AttrCond(AttrCond.Type.EQ);
+ attrEq2.setSchema("aLong");
+ attrEq2.setExpression("99");
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.or(SearchCond.of(attrEq), SearchCond.of(attrEq2)),
+ Pageable.unpaged());
+ assertTrue(result.stream().anyMatch(r -> "two".equals(r.getName())));
+ assertTrue(result.stream().anyMatch(r -> "odd".equals(r.getName())));
+
+ AttrCond attrIsNull = new AttrCond(AttrCond.Type.ISNULL);
+ attrIsNull.setSchema("aLong");
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNull),
+ Pageable.unpaged());
+ result.forEach(r -> assertFalse("two".equals(r.getName()) || "odd".equals(r.getName())));
+
+ AttrCond attrIsNotNull = new AttrCond(AttrCond.Type.ISNOTNULL);
+ attrIsNotNull.setSchema("aLong");
+
+ assertEquals(2, realmSearchDAO.count(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNotNull)));
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNotNull),
+ Pageable.unpaged(Sort.by(new Sort.Order(Sort.Direction.ASC, "name"))));
+ assertEquals("odd", result.get(0).getName());
+ assertEquals("two", result.get(1).getName());
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNotNull),
+ Pageable.unpaged(Sort.by(new Sort.Order(Sort.Direction.ASC, "aLong"))));
+ assertEquals("two", result.get(0).getName());
+ assertEquals("odd", result.get(1).getName());
+
+ AttrCond cond1 = new AttrCond(AttrCond.Type.EQ);
+ cond1.setSchema("aLong");
+ cond1.setExpression("42");
+ AttrCond cond2 = new AttrCond(AttrCond.Type.IEQ);
+ cond2.setSchema("ctype");
+ cond2.setExpression("string");
+ assertDoesNotThrow(() -> realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.and(SearchCond.of(cond1), SearchCond.of(cond2)),
+ Pageable.unpaged()));
+ }
}
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 7f1a110492..a9764f8780 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
@@ -30,10 +30,12 @@ import java.time.OffsetDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.core.persistence.api.attrvalue.InvalidEntityException;
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.AnyTypeClassDAO;
import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
@@ -97,6 +99,9 @@ public class UserTest extends AbstractTest {
@Autowired
private AnyTypeClassDAO anyTypeClassDAO;
+ @Autowired
+ private AnySearchDAO anySearchDAO;
+
@Autowired
private PlainAttrValidationManager validator;
@@ -306,13 +311,15 @@ public class UserTest extends AbstractTest {
assertNotNull(firstname);
// search by ksuffix derived attribute
- List<User> list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("ksuffix").orElseThrow().getExpression(), firstname + 'k', false);
+ List<User> list = anySearchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("ksuffix").orElseThrow().getExpression(),
+ firstname + 'k', false, AnyTypeKind.USER);
assertEquals(1, list.size());
// search by kprefix derived attribute
- list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("kprefix").orElseThrow().getExpression(), 'k' + firstname, false);
+ list = anySearchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("kprefix").orElseThrow().getExpression(),
+ 'k' + firstname, false, AnyTypeKind.USER);
assertEquals(1, list.size());
}
}
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
index 72b23a7634..c1e50e6cef 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/PersistenceContext.java
@@ -86,10 +86,9 @@ import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.persistence.common.CommonPersistenceContext;
import org.apache.syncope.core.persistence.common.RuntimeDomainLoader;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.neo4j.content.XMLContentExporter;
import org.apache.syncope.core.persistence.neo4j.content.XMLContentLoader;
import org.apache.syncope.core.persistence.neo4j.dao.Neo4jAnyChecker;
@@ -502,7 +501,6 @@ public class PersistenceContext {
final @Lazy DerSchemaDAO derSchemaDAO,
final @Lazy UserDAO userDAO,
final @Lazy GroupDAO groupDAO,
- final @Lazy AnyFinder anyFinder,
final AnyChecker anyChecker,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
@@ -517,7 +515,6 @@ public class PersistenceContext {
userDAO,
groupDAO,
anyChecker,
- anyFinder,
neo4jTemplate,
neo4jClient,
nodeValidator,
@@ -935,7 +932,6 @@ public class PersistenceContext {
final @Lazy UserDAO userDAO,
final @Lazy AnyObjectDAO anyObjectDAO,
final AnyChecker anyChecker,
- final @Lazy AnyFinder anyFinder,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
final NodeValidator nodeValidator,
@@ -953,7 +949,6 @@ public class PersistenceContext {
userDAO,
anyObjectDAO,
anyChecker,
- anyFinder,
neo4jTemplate,
neo4jClient,
nodeValidator,
@@ -1185,11 +1180,26 @@ public class PersistenceContext {
@ConditionalOnMissingBean
@Bean
public RealmSearchDAO realmSearchDAO(
+ final @Lazy RealmDAO realmDAO,
+ final PlainSchemaDAO plainSchemaDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
+ final RealmUtils realmUtils,
final Neo4jTemplate neo4jTemplate,
- final Neo4jClient neo4jClient,
- final Cache<EntityCacheKey, Neo4jRealm> realmCache) {
+ final Neo4jClient neo4jClient) {
- return new Neo4jRealmSearchDAO(neo4jTemplate, neo4jClient, realmCache);
+ return new Neo4jRealmSearchDAO(
+ realmDAO,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ realmUtils,
+ neo4jTemplate,
+ neo4jClient);
}
@ConditionalOnMissingBean
@@ -1307,7 +1317,6 @@ public class PersistenceContext {
final @Lazy AnyMatchDAO anyMatchDAO,
final @Lazy AnySearchDAO anySearchDAO,
final DelegationDAO delegationDAO,
- final SearchCondVisitor searchCondVisitor,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
final NodeValidator nodeValidator,
@@ -1318,7 +1327,6 @@ public class PersistenceContext {
anyMatchDAO,
anySearchDAO,
delegationDAO,
- searchCondVisitor,
neo4jTemplate,
neo4jClient,
nodeValidator,
@@ -1441,7 +1449,6 @@ public class PersistenceContext {
final DelegationDAO delegationDAO,
final FIQLQueryDAO fiqlQueryDAO,
final AnyChecker anyChecker,
- final @Lazy AnyFinder anyFinder,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
final NodeValidator nodeValidator,
@@ -1460,7 +1467,6 @@ public class PersistenceContext {
delegationDAO,
fiqlQueryDAO,
anyChecker,
- anyFinder,
securityProperties,
neo4jTemplate,
neo4jClient,
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 9631c5252f..39c976238c 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
@@ -28,7 +28,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import org.apache.commons.lang3.tuple.Pair;
@@ -133,6 +132,20 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO {
: value;
}
+ protected static void queryOp(
+ final TextStringBuilder query,
+ final String op,
+ final QueryInfo leftInfo,
+ final QueryInfo rightInfo) {
+
+ query.append("WHERE EXISTS { ").
+ append(Strings.CS.prependIfMissing(leftInfo.query().toString(), "MATCH (n) ")).
+ append(" } ").
+ append(op).append(" EXISTS { ").
+ append(Strings.CS.prependIfMissing(rightInfo.query().toString(), "MATCH (n) ")).
+ append(" }");
+ }
+
protected final Neo4jTemplate neo4jTemplate;
protected final Neo4jClient neo4jClient;
@@ -198,7 +211,8 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO {
return noRealm;
});
- realmKeys.addAll(realmSearchDAO.findDescendants(realm.getFullPath(), base.getFullPath()));
+ realmKeys.addAll(realmSearchDAO.findDescendants(realm.getFullPath(), base.getFullPath()).
+ stream().map(Realm::getKey).toList());
}));
} else {
if (adminRealms.stream().anyMatch(r -> r.startsWith(base.getFullPath()))) {
@@ -605,12 +619,13 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO {
null);
}
- CheckResult<AnyCond> checked = check(cond, kind);
-
- if (ArrayUtils.contains(
- RELATIONSHIP_FIELDS,
- StringUtils.substringBefore(checked.cond().getSchema(), "_id"))) {
+ CheckResult<AnyCond> checked = check(
+ cond,
+ anyUtilsFactory.getInstance(kind).getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
+ if (RELATIONSHIP_FIELDS.contains(StringUtils.substringBefore(checked.cond().getSchema(), "_id"))) {
String field = StringUtils.substringBefore(checked.cond().getSchema(), "_id");
switch (field) {
case "uManager" -> {
@@ -673,20 +688,6 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO {
// do nothing by default, leave it open for subclasses
}
- protected void queryOp(
- final TextStringBuilder query,
- final String op,
- final QueryInfo leftInfo,
- final QueryInfo rightInfo) {
-
- query.append("WHERE EXISTS { ").
- append(Strings.CS.prependIfMissing(leftInfo.query().toString(), "MATCH (n) ")).
- append(" } ").
- append(op).append(" EXISTS { ").
- append(Strings.CS.prependIfMissing(rightInfo.query().toString(), "MATCH (n) ")).
- append(" }");
- }
-
protected QueryInfo getQuery(final AnyTypeKind kind, final SearchCond cond, final Map<String, Object> parameters) {
boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
@@ -925,6 +926,8 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO {
// 4. prepare the count query
query.append("RETURN COUNT(id)");
+ LOG.debug("Query: {}, parameters: {}", query, parameters);
+
return neo4jTemplate.count(query.toString(), parameters);
}
@@ -1001,7 +1004,7 @@ public class Neo4jAnySearchDAO extends AbstractAnySearchDAO {
append(" LIMIT ").append(pageable.getPageSize());
}
- LOG.debug("Query with auth and order by statements: {}, parameters: {}", query, parameters);
+ LOG.debug("Query: {}, parameters: {}", query, parameters);
// 5. Prepare the result (avoiding duplicates)
return buildResult(neo4jClient.query(query.toString()).bindAll(parameters).fetch().all().stream().
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmDAO.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmDAO.java
index 8c9bfd9d10..2701d0850f 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmDAO.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmDAO.java
@@ -266,7 +266,7 @@ public class Neo4jRealmDAO extends AbstractDAO implements RealmDAO {
return;
}
- realmSearchDAO.findDescendants(realm.getFullPath(), null, Pageable.unpaged()).forEach(toBeDeleted -> {
+ realmSearchDAO.findDescendants(realm.getFullPath(), null).forEach(toBeDeleted -> {
roleDAO.findByRealms(toBeDeleted).forEach(role -> role.getRealms().remove(toBeDeleted));
cascadeDelete(
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java
index 753879b62d..6302c3970f 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jRealmSearchDAO.java
@@ -18,62 +18,129 @@
*/
package org.apache.syncope.core.persistence.neo4j.dao;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
-import javax.cache.Cache;
+import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Strings;
+import org.apache.commons.text.TextStringBuilder;
+import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
-import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
+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.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+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.Realm;
-import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
+import org.apache.syncope.core.persistence.common.dao.AbstractRealmSearchDAO;
+import org.apache.syncope.core.persistence.neo4j.dao.repo.AnyRepoExt;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRealm;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Order;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.core.Neo4jTemplate;
+import org.springframework.data.util.Streamable;
import org.springframework.transaction.annotation.Transactional;
-public class Neo4jRealmSearchDAO extends AbstractDAO implements RealmSearchDAO {
+public class Neo4jRealmSearchDAO extends AbstractRealmSearchDAO {
- protected static StringBuilder buildDescendantsQuery(
- final Set<String> bases,
- final String keyword,
- final Map<String, Object> parameters) {
+ protected record AnyCondQuery(String query, String field) {
- AtomicInteger index = new AtomicInteger(0);
- String basesClause = bases.stream().map(base -> {
- int idx = index.incrementAndGet();
- parameters.put("base" + idx, base);
- parameters.put("like" + idx, SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
- return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" + idx;
- }).collect(Collectors.joining(" OR "));
+ }
- StringBuilder queryString = new StringBuilder("MATCH (n:").append(Neo4jRealm.NODE).append(") ").
- append("WHERE (").append(basesClause).append(')');
+ protected record AttrCondQuery(String query, PlainSchema schema) {
- if (keyword != null) {
- queryString.append(" AND toLower(n.name) =~ $name");
- parameters.put("name", keyword.replace("%", ".*").replaceAll("_", "\\\\_").toLowerCase() + ".*");
+ }
+
+ protected record QueryInfo(
+ TextStringBuilder query,
+ Set<String> fields,
+ Set<PlainSchema> plainSchemas) {
+
+ }
+
+ protected static String setParameter(final Map<String, Object> parameters, final Object parameter) {
+ String name = "param" + parameters.size();
+ parameters.put(name, parameter);
+ return name;
+ }
+
+ protected static void appendPlainAttrCond(
+ final TextStringBuilder query, final PlainSchema schema, final String cond) {
+
+ if (schema.isUniqueConstraint()) {
+ query.append(schema.getKey()).append('.').append(key(schema.getType())).append(cond);
+ } else {
+ query.append("any(k IN ").append(schema.getKey()).
+ append(" WHERE k").append('.').append(key(schema.getType())).append(cond).
+ append(")");
}
+ }
- return queryString;
+ protected static String escapeIfString(final String value, final boolean isStr) {
+ return isStr
+ ? new StringBuilder().append('"').append(value).append('"').toString()
+ : value;
}
- protected final Cache<EntityCacheKey, Neo4jRealm> cache;
+ protected static void queryOp(
+ final TextStringBuilder query,
+ final String op,
+ final QueryInfo leftInfo,
+ final QueryInfo rightInfo) {
+
+ query.append("WHERE EXISTS { ").
+ append(Strings.CS.prependIfMissing(leftInfo.query().toString(), "MATCH (n) ")).
+ append(" } ").
+ append(op).append(" EXISTS { ").
+ append(Strings.CS.prependIfMissing(rightInfo.query().toString(), "MATCH (n) ")).
+ append(" }");
+ }
+
+ protected final RealmDAO realmDAO;
+
+ protected final RealmUtils realmUtils;
+
+ protected final Neo4jTemplate neo4jTemplate;
+
+ protected final Neo4jClient neo4jClient;
public Neo4jRealmSearchDAO(
+ final RealmDAO realmDAO,
+ final PlainSchemaDAO plainSchemaDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
+ final RealmUtils realmUtils,
final Neo4jTemplate neo4jTemplate,
- final Neo4jClient neo4jClient,
- final Cache<EntityCacheKey, Neo4jRealm> cache) {
+ final Neo4jClient neo4jClient) {
- super(neo4jTemplate, neo4jClient);
- this.cache = cache;
+ super(plainSchemaDAO, userDAO, groupDAO, entityFactory, validator);
+ this.realmDAO = realmDAO;
+ this.realmUtils = realmUtils;
+ this.neo4jTemplate = neo4jTemplate;
+ this.neo4jClient = neo4jClient;
}
@Transactional(readOnly = true)
@@ -89,68 +156,518 @@ public class Neo4jRealmSearchDAO extends AbstractDAO implements RealmSearchDAO {
return neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.fullPath = $fullPath RETURN n.id").
bindAll(Map.of("fullPath", fullPath)).fetch().one().
- flatMap(toOptional("n.id", Neo4jRealm.class, cache));
+ flatMap(found -> realmDAO.findById(found.get("n.id").toString()).map(n -> (Realm) n));
+ }
+
+ protected List<Realm> toList(
+ final Collection<Map<String, Object>> result,
+ final String property) {
+
+ return result.stream().
+ map(found -> realmDAO.findById(found.get(property).toString())).
+ flatMap(Optional::stream).map(n -> (Realm) n).toList();
}
@Override
public List<Realm> findByName(final String name) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + ") WHERE n.name = $name RETURN n.id").
- bindAll(Map.of("name", name)).fetch().all(), "n.id", Neo4jRealm.class, cache);
+ bindAll(Map.of("name", name)).fetch().all(), "n.id");
}
@Override
public List<Realm> findChildren(final Realm realm) {
return toList(neo4jClient.query(
"MATCH (n:" + Neo4jRealm.NODE + " {id: $id})<-[r:" + Neo4jRealm.PARENT_REL + "]-(c) RETURN c.id").
- bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id", Neo4jRealm.class, cache);
+ bindAll(Map.of("id", realm.getKey())).fetch().all(), "c.id");
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ public List<Realm> findDescendants(final String base, final String prefix) {
+ Map<String, Object> parameters = new HashMap<>();
+
+ StringBuilder query = new StringBuilder("MATCH (n:").append(Neo4jRealm.NODE).append(") ").
+ append("WHERE (").append("n.fullPath = $base OR n.fullPath =~ $like").append(')');
+ parameters.put("base", base);
+ parameters.put("like", SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+
+ if (prefix != null) {
+ query.append(" AND (n.fullPath = $prefix OR n.fullPath =~ $likePrefix)");
+ parameters.put("prefix", prefix);
+ parameters.put("likePrefix", SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ }
+
+ query.append(" RETURN n.id ORDER BY n.fullPath");
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "n.id");
}
- @Override
- public long countDescendants(final Set<String> bases, final String keyword) {
- Map<String, Object> parameters = new HashMap<>();
+ protected QueryInfo getQuery(final SearchCond cond, final Map<String, Object> parameters) {
+ boolean not = cond.getType() == SearchCond.Type.NOT_LEAF;
+
+ TextStringBuilder query = new TextStringBuilder();
+ Set<String> involvedFields = new HashSet<>();
+ Set<PlainSchema> involvedPlainSchemas = new HashSet<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword, parameters).append(" RETURN COUNT(n)");
- return neo4jTemplate.count(queryString.toString(), parameters);
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ cond.asLeaf(AnyCond.class).ifPresentOrElse(
+ anyCond -> {
+ AnyCondQuery anyCondQuery = getQuery(anyCond, not, parameters);
+ query.append(anyCondQuery.query());
+ Optional.ofNullable(anyCondQuery.field()).ifPresent(involvedFields::add);
+ },
+ () -> cond.asLeaf(AttrCond.class).ifPresent(leaf -> {
+ AttrCondQuery attrCondQuery = getQuery(leaf, not, parameters);
+ query.append(attrCondQuery.query());
+ involvedPlainSchemas.add(attrCondQuery.schema());
+ }));
+
+ // allow for additional search conditions
+ getQueryForCustomConds(cond, parameters, not, query);
+ }
+ case AND -> {
+ QueryInfo leftAndInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftAndInfo.fields());
+ involvedPlainSchemas.addAll(leftAndInfo.plainSchemas());
+
+ QueryInfo rigthAndInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthAndInfo.fields());
+ involvedPlainSchemas.addAll(rigthAndInfo.plainSchemas());
+
+ queryOp(query, "AND", leftAndInfo, rigthAndInfo);
+ }
+
+ case OR -> {
+ QueryInfo leftOrInfo = getQuery(cond.getLeft(), parameters);
+ involvedFields.addAll(leftOrInfo.fields());
+ involvedPlainSchemas.addAll(leftOrInfo.plainSchemas());
+
+ QueryInfo rigthOrInfo = getQuery(cond.getRight(), parameters);
+ involvedFields.addAll(rigthOrInfo.fields());
+ involvedPlainSchemas.addAll(rigthOrInfo.plainSchemas());
+
+ queryOp(query, "OR", leftOrInfo, rigthOrInfo);
+ }
+
+ default -> {
+ }
+ }
+
+ return new QueryInfo(query, involvedFields, involvedPlainSchemas);
}
- @Override
- public List<Realm> findDescendants(final String base, final String keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected void wrapQuery(
+ final Set<String> bases,
+ final QueryInfo queryInfo,
+ final Streamable<Order> orderBy,
+ final Map<String, Object> parameters) {
+
+ TextStringBuilder match = new TextStringBuilder("MATCH (n:").append(Neo4jRealm.NODE).append(") ").
+ append("WITH n.id AS id");
+
+ // take fields into account
+ queryInfo.fields().remove("id");
+ Stream.concat(
+ queryInfo.fields().stream(),
+ orderBy.stream().filter(clause -> !"id".equals(clause.getProperty())
+ && realmUtils.getField(clause.getProperty()).isPresent()).map(Order::getProperty)).
+ distinct().forEach(field -> match.append(", n.").append(field).append(" AS ").append(field));
+
+ // take plain schemas into account
+ Stream.concat(
+ queryInfo.plainSchemas().stream(),
+ orderBy.stream().map(clause -> plainSchemaDAO.findById(clause.getProperty())).
+ flatMap(Optional::stream)).distinct().forEach(schema -> {
+
+ match.append(", apoc.convert.getJsonProperty(n, 'plainAttrs.").append(schema.getKey());
+ if (schema.isUniqueConstraint()) {
+ match.append("', '$.uniqueValue')");
+ } else {
+ match.append("', '$.values')");
+ }
+ match.append(" AS ").append(schema.getKey());
+ });
+
+ TextStringBuilder query = queryInfo.query();
+
+ // take bases into account
+ AtomicInteger index = new AtomicInteger(0);
+ String basesClause = bases.stream().map(base -> {
+ int idx = index.incrementAndGet();
+ parameters.put("base" + idx, base);
+ parameters.put("like" + idx, SyncopeConstants.ROOT_REALM.equals(base) ? "/.*" : base + "/.*");
+ return "n.fullPath = $base" + idx + " OR n.fullPath =~ $like" + idx;
+ }).collect(Collectors.joining(" OR "));
+ if (query.startsWith("MATCH (n)")) {
+ query.replaceFirst("MATCH (n)", match + " WHERE (EXISTS { MATCH (n)");
+ query.append("} ");
+ } else {
+ query.replaceFirst("WHERE EXISTS", "WHERE (EXISTS");
+ query.insert(0, match.append(' '));
+ }
+ query.append(") AND EXISTS { ").append("(n) WHERE (").append(basesClause).append(")").append(" } ");
+ }
+
+ protected AttrCondQuery getQuery(
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AttrCond> checked = check(cond);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) ");
+ switch (cond.getType()) {
+ case ISNOTNULL ->
+ query.append("WHERE n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NOT NULL");
+
+ case ISNULL ->
+ query.append("WHERE n.`plainAttrs.").append(checked.schema().getKey()).append("` IS NULL");
+
+ default ->
+ fillAttrQuery(query, checked.value(), checked.schema(), cond, not, parameters);
+ }
+
+ return new AttrCondQuery(query.toString(), checked.schema());
+ }
+
+ protected void getQueryForCustomConds(
+ final SearchCond cond,
+ final Map<String, Object> parameters,
+ final boolean not,
+ final TextStringBuilder query) {
+
+ // do nothing by default, leave it open for subclasses
+ }
+
+ protected void fillAttrQuery(
+ final TextStringBuilder query,
+ final PlainAttrValue attrValue,
+ final PlainSchema schema,
+ final AttrCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ if (not && cond.getType() == AttrCond.Type.ISNULL) {
+ cond.setType(AttrCond.Type.ISNOTNULL);
+ fillAttrQuery(query, attrValue, schema, cond, true, parameters);
+ return;
+ }
+ if (not) {
+ if (schema.isUniqueConstraint()) {
+ fillAttrQuery(query, attrValue, schema, cond, false, parameters);
+ query.replaceFirst("WHERE", "WHERE NOT(");
+ query.append(')');
+ } else {
+ fillAttrQuery(query, attrValue, schema, cond, false, parameters);
+ query.replaceAll("any(", schema.getKey() + " IS NULL OR none(");
+ }
+ return;
+ }
+
+ String value = Optional.ofNullable(attrValue.getDateValue()).
+ map(DateTimeFormatter.ISO_OFFSET_DATE_TIME::format).
+ orElseGet(cond::getExpression);
+
+ boolean isStr = true;
+ boolean lower = false;
+ if (schema.getType().isStringClass()) {
+ lower = (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
+ } else if (schema.getType() != AttrSchemaType.Date) {
+ lower = false;
+ try {
+ switch (schema.getType()) {
+ case Long ->
+ Long.valueOf(value);
+
+ case Double ->
+ Double.valueOf(value);
+
+ case Boolean -> {
+ if (!("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value))) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ default -> {
+ }
+ }
+
+ isStr = false;
+ } catch (Exception nfe) {
+ // ignore
+ }
+ }
+
+ query.append("WHERE ");
+
+ switch (cond.getType()) {
+ case ISNULL -> {
+ }
+
+ case ISNOTNULL ->
+ query.append(schema.getKey()).append(" IS NOT NULL");
+
+ case ILIKE, LIKE -> {
+ if (schema.getType().isStringClass()) {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " =~ \"" + (lower ? "(?i)" : "")
+ + AnyRepoExt.escapeForLikeRegex(value).replace("%", ".*") + '"');
+ } else {
+ query.append(ALWAYS_FALSE_CLAUSE);
+ LOG.error("LIKE is only compatible with string or enum schemas");
+ }
+ }
+
+ case IEQ, EQ -> {
+ if (StringUtils.containsAny(value, AnyRepoExt.REGEX_CHARS) || lower) {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " =~ \"^" + (lower ? "(?i)" : "")
+ + AnyRepoExt.escapeForLikeRegex(value).replace("%", ".*") + "$\"");
+ } else {
+ appendPlainAttrCond(
+ query,
+ schema,
+ " = " + escapeIfString(value, isStr));
+ }
+ }
+
+ case GE ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " >= " + escapeIfString(value, isStr));
+
+ case GT ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " > " + escapeIfString(value, isStr));
+
+ case LE ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " <= " + escapeIfString(value, isStr));
+
+ case LT ->
+ appendPlainAttrCond(
+ query,
+ schema,
+ " < " + escapeIfString(value, isStr));
+
+ default -> {
+ }
+ }
+ // shouldn't occour: processed before
+ }
+
+ protected void fillAttrQuery(
+ final TextStringBuilder query,
+ final PlainAttrValue attrValue,
+ final PlainSchema schema,
+ final AnyCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ if (not && cond.getType() == AttrCond.Type.ISNULL) {
+ cond.setType(AttrCond.Type.ISNOTNULL);
+ fillAttrQuery(query, attrValue, schema, cond, true, parameters);
+ return;
+ }
+ if (not) {
+ query.append("NOT (");
+ fillAttrQuery(query, attrValue, schema, cond, false, parameters);
+ query.append(')');
+ return;
+ }
+ if (not && cond.getType() == AttrCond.Type.ISNULL) {
+ cond.setType(AttrCond.Type.ISNOTNULL);
+ fillAttrQuery(query, attrValue, schema, cond, true, parameters);
+ return;
+ }
+
+ boolean lower = schema.getType().isStringClass()
+ && (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
+
+ String property = "n." + cond.getSchema();
+ if (lower) {
+ property = "toLower (" + property + ')';
+ }
+
+ switch (cond.getType()) {
+
+ case ISNULL ->
+ query.append(property).append(" IS NULL");
+
+ case ISNOTNULL ->
+ query.append(property).append(" IS NOT NULL");
+
+ case ILIKE, LIKE -> {
+ if (schema.getType().isStringClass()) {
+ query.append(property).append(" =~ ");
+ if (lower) {
+ query.append("toLower($").
+ append(setParameter(parameters, cond.getExpression().replace("%", ".*"))).
+ append(')');
+ } else {
+ query.append('$').append(setParameter(parameters, cond.getExpression().replace("%", ".*")));
+ }
+ } else {
+ query.append(' ').append(ALWAYS_FALSE_CLAUSE);
+ LOG.error("LIKE is only compatible with string or enum schemas");
+ }
+ }
+
+ case IEQ, EQ -> {
+ query.append(property).append('=');
+
+ if (lower) {
+ query.append("toLower($").append(setParameter(parameters, attrValue.getValue())).append(')');
+ } else {
+ query.append('$').append(setParameter(parameters, attrValue.getValue()));
+ }
+ }
+
+ case GE -> {
+ query.append(property);
+ if (not) {
+ query.append('<');
+ } else {
+ query.append(">=");
+ }
+ query.append('$').append(setParameter(parameters, attrValue.getValue()));
+ }
+
+ case GT -> {
+ query.append(property);
+ if (not) {
+ query.append("<=");
+ } else {
+ query.append('>');
+ }
+ query.append('$').append(setParameter(parameters, attrValue.getValue()));
+ }
+
+ case LE -> {
+ query.append(property);
+ if (not) {
+ query.append('>');
+ } else {
+ query.append("<=");
+ }
+ query.append('$').append(setParameter(parameters, attrValue.getValue()));
+ }
+
+ case LT -> {
+ query.append(property);
+ if (not) {
+ query.append(">=");
+ } else {
+ query.append('<');
+ }
+ query.append('$').append(setParameter(parameters, attrValue.getValue()));
+ }
+
+ default -> {
+ }
+ }
+ }
+
+ protected AnyCondQuery getQuery(
+ final AnyCond cond,
+ final boolean not,
+ final Map<String, Object> parameters) {
+
+ CheckResult<AnyCond> checked = check(
+ cond,
+ realmUtils.getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
+
+ TextStringBuilder query = new TextStringBuilder("MATCH (n) WHERE ");
+
+ fillAttrQuery(query, checked.value(), checked.schema(), checked.cond(), not, parameters);
+
+ return new AnyCondQuery(query.toString(), checked.cond().getSchema());
}
@Override
- public List<Realm> findDescendants(final Set<String> bases, final String keyword, final Pageable pageable) {
+ protected long doCount(final Set<String> bases, final SearchCond cond) {
Map<String, Object> parameters = new HashMap<>();
- StringBuilder queryString = buildDescendantsQuery(bases, keyword, parameters).
- append(" RETURN n.id ORDER BY n.fullPath");
- if (pageable.isPaged()) {
- queryString.append(" SKIP ").append(pageable.getPageSize() * pageable.getPageNumber()).
- append(" LIMIT ").append(pageable.getPageSize());
- }
+ QueryInfo queryInfo = getQuery(cond, parameters);
- return toList(neo4jClient.query(
- queryString.toString()).bindAll(parameters).fetch().all(), "n.id", Neo4jRealm.class, cache);
+ wrapQuery(bases, queryInfo, Streamable.empty(), parameters);
+ TextStringBuilder query = queryInfo.query();
+
+ query.append("RETURN COUNT(id)");
+
+ LOG.debug("Query: {}, parameters: {}", query, parameters);
+
+ return neo4jTemplate.count(query.toString(), parameters);
+ }
+
+ protected List<String> parseOrderBy(final Streamable<Sort.Order> orderBy) {
+ List<String> clauses = new ArrayList<>();
+
+ Set<String> orderByUniquePlainSchemas = new HashSet<>();
+ Set<String> orderByNonUniquePlainSchemas = new HashSet<>();
+ orderBy.forEach(clause -> {
+ if (realmUtils.getField(clause.getProperty()).isPresent()) {
+ clauses.add(clause.getProperty() + " " + clause.getDirection().name());
+ } else {
+ plainSchemaDAO.findById(clause.getProperty()).ifPresent(schema -> {
+ if (schema.isUniqueConstraint()) {
+ orderByUniquePlainSchemas.add(schema.getKey());
+ } else {
+ orderByNonUniquePlainSchemas.add(schema.getKey());
+ }
+ if (orderByUniquePlainSchemas.size() > 1 || orderByNonUniquePlainSchemas.size() > 1) {
+ SyncopeClientException invalidSearch =
+ SyncopeClientException.build(ClientExceptionType.InvalidSearchParameters);
+ invalidSearch.getElements().add("Order by more than one attribute is not allowed; "
+ + "remove one from " + (orderByUniquePlainSchemas.size() > 1
+ ? orderByUniquePlainSchemas : orderByNonUniquePlainSchemas));
+ throw invalidSearch;
+ }
+
+ clauses.add(schema.getKey() + " " + clause.getDirection().name());
+ });
+ }
+ });
+
+ return clauses;
}
@Override
- public List<String> findDescendants(final String base, final String prefix) {
+ protected List<Realm> doSearch(final Set<String> bases, final SearchCond cond, final Pageable pageable) {
Map<String, Object> parameters = new HashMap<>();
- StringBuilder queryString = buildDescendantsQuery(Set.of(base), null, parameters).
- append(" AND (n.fullPath = $prefix OR n.fullPath =~ $likePrefix)").
- append(" RETURN n.id ORDER BY n.fullPath");
- parameters.put("prefix", prefix);
- parameters.put("likePrefix", SyncopeConstants.ROOT_REALM.equals(prefix) ? "/.*" : prefix + "/.*");
+ QueryInfo queryInfo = getQuery(cond, parameters);
- return neo4jClient.query(queryString.toString()).
- bindAll(parameters).fetch().all().stream().
- map(found -> (String) found.values().iterator().next()).toList();
+ wrapQuery(bases, queryInfo, pageable.getSort(), parameters);
+ TextStringBuilder query = queryInfo.query();
+
+ List<String> orderBy = parseOrderBy(pageable.getSort());
+ String orderByStmt = String.join(", ", orderBy);
+
+ query.append("RETURN id ").
+ append("ORDER BY ").append(orderByStmt);
+
+ if (pageable.isPaged()) {
+ query.append(" SKIP ").append(pageable.getPageSize() * pageable.getPageNumber()).
+ append(" LIMIT ").append(pageable.getPageSize());
+ }
+
+ LOG.debug("Query: {}, parameters: {}", query, parameters);
+
+ return toList(neo4jClient.query(
+ query.toString()).bindAll(parameters).fetch().all(), "id");
}
}
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java
index 0251ab76c2..0bbea61c08 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/Neo4jTaskDAO.java
@@ -320,7 +320,7 @@ public class Neo4jTaskDAO extends AbstractDAO implements TaskDAO {
Stream<String> realmKeys = AuthContextUtils.getAuthorizations().get(IdRepoEntitlement.TASK_LIST).stream().
map(realmSearchDAO::findByFullPath).
filter(Optional::isPresent).
- flatMap(r -> realmSearchDAO.findDescendants(r.get().getFullPath(), null, Pageable.unpaged()).
+ flatMap(r -> realmSearchDAO.findDescendants(r.get().getFullPath(), null).
stream()).
map(Realm::getKey).
distinct();
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 b82ded261f..ea7569315a 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
@@ -38,7 +38,6 @@ 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.GRelationship;
import org.apache.syncope.core.persistence.api.entity.user.URelationship;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.neo4j.dao.AbstractDAO;
import org.apache.syncope.core.persistence.neo4j.entity.AbstractAny;
import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
@@ -84,8 +83,6 @@ public abstract class AbstractAnyRepoExt<A extends Any, N extends AbstractAny>
protected final AnyChecker anyChecker;
- protected final AnyFinder anyFinder;
-
protected final AnyUtils anyUtils;
protected AbstractAnyRepoExt(
@@ -93,7 +90,6 @@ public abstract class AbstractAnyRepoExt<A extends Any, N extends AbstractAny>
final AnyTypeClassDAO anyTypeClassDAO,
final DerSchemaDAO derSchemaDAO,
final AnyChecker anyChecker,
- final AnyFinder anyFinder,
final AnyUtils anyUtils,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient) {
@@ -103,7 +99,6 @@ public abstract class AbstractAnyRepoExt<A extends Any, N extends AbstractAny>
this.anyTypeClassDAO = anyTypeClassDAO;
this.derSchemaDAO = derSchemaDAO;
this.anyChecker = anyChecker;
- this.anyFinder = anyFinder;
this.anyUtils = anyUtils;
}
@@ -150,11 +145,6 @@ public abstract class AbstractAnyRepoExt<A extends Any, N extends AbstractAny>
return any;
}
- @Override
- public List<A> findByDerAttrValue(final String expression, final String value, final boolean ignoreCaseMatch) {
- return anyFinder.findByDerAttrValue(anyUtils.anyTypeKind(), expression, value, ignoreCaseMatch);
- }
-
@Override
public List<A> findByResourcesContaining(final ExternalResource resource) {
return findByRelationship(
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java
index 6504b21a99..606f8efddd 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/AnyObjectRepoExtImpl.java
@@ -47,7 +47,6 @@ 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.URelationship;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyType;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass;
@@ -85,7 +84,6 @@ public class AnyObjectRepoExtImpl extends AbstractAnyRepoExt<AnyObject, Neo4jAny
final UserDAO userDAO,
final GroupDAO groupDAO,
final AnyChecker anyChecker,
- final AnyFinder anyFinder,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
final NodeValidator nodeValidator,
@@ -96,7 +94,6 @@ public class AnyObjectRepoExtImpl extends AbstractAnyRepoExt<AnyObject, Neo4jAny
anyTypeClassDAO,
derSchemaDAO,
anyChecker,
- anyFinder,
anyUtilsFactory.getInstance(AnyTypeKind.ANY_OBJECT),
neo4jTemplate,
neo4jClient);
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 6fd68e4add..9b0ae140a7 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
@@ -75,8 +75,6 @@ public interface AnyRepoExt<A extends Any> {
A authFind(String key);
- List<A> findByDerAttrValue(String expression, String value, boolean ignoreCaseMatch);
-
Collection<String> findAllResourceKeys(String key);
List<A> findByResourcesContaining(ExternalResource resource);
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 95bcb2704b..6fcb2ac443 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
@@ -47,7 +47,6 @@ import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension;
import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.neo4j.entity.AbstractAny;
import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass;
@@ -98,7 +97,6 @@ public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group, Neo4jGroup> impl
final UserDAO userDAO,
final AnyObjectDAO anyObjectDAO,
final AnyChecker anyChecker,
- final AnyFinder anyFinder,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
final NodeValidator nodeValidator,
@@ -111,11 +109,10 @@ public class GroupRepoExtImpl extends AbstractAnyRepoExt<Group, Neo4jGroup> impl
anyTypeClassDAO,
derSchemaDAO,
anyChecker,
- anyFinder,
anyUtilsFactory.getInstance(AnyTypeKind.GROUP),
neo4jTemplate,
neo4jClient);
-
+
this.publisher = publisher;
this.realmDAO = realmDAO;
this.userDAO = userDAO;
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RoleRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RoleRepoExtImpl.java
index fee8671f33..67e1b6dd39 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RoleRepoExtImpl.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/RoleRepoExtImpl.java
@@ -26,7 +26,6 @@ import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.persistence.neo4j.dao.AbstractDAO;
import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jRealm;
@@ -50,8 +49,6 @@ public class RoleRepoExtImpl extends AbstractDAO implements RoleRepoExt {
protected final DelegationDAO delegationDAO;
- protected final SearchCondVisitor searchCondVisitor;
-
protected final NodeValidator nodeValidator;
protected final Cache<EntityCacheKey, Neo4jRole> cache;
@@ -61,7 +58,6 @@ public class RoleRepoExtImpl extends AbstractDAO implements RoleRepoExt {
final AnyMatchDAO anyMatchDAO,
final AnySearchDAO anySearchDAO,
final DelegationDAO delegationDAO,
- final SearchCondVisitor searchCondVisitor,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
final NodeValidator nodeValidator,
@@ -72,7 +68,6 @@ public class RoleRepoExtImpl extends AbstractDAO implements RoleRepoExt {
this.anyMatchDAO = anyMatchDAO;
this.anySearchDAO = anySearchDAO;
this.delegationDAO = delegationDAO;
- this.searchCondVisitor = searchCondVisitor;
this.nodeValidator = nodeValidator;
this.cache = cache;
}
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java
index 4cd720ded8..3dcb0997f8 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/UserRepoExtImpl.java
@@ -51,7 +51,6 @@ import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.persistence.api.entity.user.URelationship;
import org.apache.syncope.core.persistence.api.entity.user.User;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
-import org.apache.syncope.core.persistence.common.dao.AnyFinder;
import org.apache.syncope.core.persistence.neo4j.entity.AbstractAny;
import org.apache.syncope.core.persistence.neo4j.entity.EntityCacheKey;
import org.apache.syncope.core.persistence.neo4j.entity.Neo4jAnyTypeClass;
@@ -107,7 +106,6 @@ public class UserRepoExtImpl extends AbstractAnyRepoExt<User, Neo4jUser> impleme
final DelegationDAO delegationDAO,
final FIQLQueryDAO fiqlQueryDAO,
final AnyChecker anyChecker,
- final AnyFinder anyFinder,
final SecurityProperties securityProperties,
final Neo4jTemplate neo4jTemplate,
final Neo4jClient neo4jClient,
@@ -121,7 +119,6 @@ public class UserRepoExtImpl extends AbstractAnyRepoExt<User, Neo4jUser> impleme
anyTypeClassDAO,
derSchemaDAO,
anyChecker,
- anyFinder,
anyUtilsFactory.getInstance(AnyTypeKind.USER),
neo4jTemplate,
neo4jClient);
diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jSRARoute.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jSRARoute.java
index e000ea4280..eaed66f437 100644
--- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jSRARoute.java
+++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/Neo4jSRARoute.java
@@ -148,7 +148,7 @@ public class Neo4jSRARoute extends AbstractGeneratedKeyNode implements SRARoute
public List<SRARouteFilter> getFilters() {
return Optional.ofNullable(filters).
map(f -> List.of(POJOHelper.deserialize(f, SRARouteFilter[].class))).
- orElseGet(List::of);
+ orElseGet(List::of);
}
@Override
@@ -160,7 +160,7 @@ public class Neo4jSRARoute extends AbstractGeneratedKeyNode implements SRARoute
public List<SRARoutePredicate> getPredicates() {
return Optional.ofNullable(predicates).
map(f -> List.of(POJOHelper.deserialize(f, SRARoutePredicate[].class))).
- orElseGet(List::of);
+ orElseGet(List::of);
}
@Override
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 8e50996632..693ff9a01a 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
@@ -39,6 +39,7 @@ import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationMana
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.AnyTypeDAO;
+import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
@@ -111,6 +112,9 @@ public class AnySearchTest extends AbstractTest {
@Autowired
private PlainSchemaDAO plainSchemaDAO;
+ @Autowired
+ private DerSchemaDAO derSchemaDAO;
+
@Autowired
private PlainAttrValidationManager validator;
@@ -761,13 +765,13 @@ public class AnySearchTest extends AbstractTest {
assertEquals(
1,
searchDAO.count(
- realmDAO.getRoot(), true, authRealms, groupDAO.getAllMatchingCond(), AnyTypeKind.GROUP));
+ realmDAO.getRoot(), true, authRealms, searchDAO.getAllMatchingCond(), AnyTypeKind.GROUP));
List<Group> groups = searchDAO.search(
realmDAO.getRoot(),
true,
authRealms,
- groupDAO.getAllMatchingCond(),
+ searchDAO.getAllMatchingCond(),
PageRequest.of(0, 10),
AnyTypeKind.GROUP);
assertEquals(1, groups.size());
@@ -794,6 +798,35 @@ public class AnySearchTest extends AbstractTest {
assertEquals(5, users.size());
}
+ @Test
+ public void findByDerAttrValue() {
+ List<User> list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(), "Vivaldi, Antonio", false, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+
+ list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", false, AnyTypeKind.USER);
+ assertEquals(0, list.size());
+
+ list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", true, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void findByInvalidDerAttrValue() {
+ assertTrue(searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("cn").orElseThrow().getExpression(),
+ "Antonio, Maria, Rossi", false, AnyTypeKind.USER).isEmpty());
+ }
+
+ @Test
+ public void findByInvalidDerAttrExpression() {
+ assertThrows(IllegalArgumentException.class, () -> searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("noschema").orElseThrow().getExpression(),
+ "Antonio, Maria", false, AnyTypeKind.USER).isEmpty());
+ }
+
@Test
public void issue202() {
ResourceCond ws2 = new ResourceCond();
diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/MultitenancyTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/MultitenancyTest.java
index 594124a0ad..dfc7ee33e1 100644
--- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/MultitenancyTest.java
+++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/MultitenancyTest.java
@@ -34,7 +34,6 @@ import org.apache.syncope.core.persistence.neo4j.PersistenceTestContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@@ -70,10 +69,10 @@ public class MultitenancyTest extends AbstractTest {
public void readRealm() {
assertEquals(
1,
- realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, Pageable.unpaged()).size());
+ realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null).size());
assertEquals(
realmDAO.getRoot(),
- realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, Pageable.unpaged()).getFirst());
+ realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null).getFirst());
}
@Test
diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/RealmTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/RealmTest.java
index 578cb3f891..ad53b741b5 100644
--- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/RealmTest.java
+++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/RealmTest.java
@@ -86,14 +86,11 @@ public class RealmTest extends AbstractTest {
@Test
public void findDescendants() {
- List<String> found = realmSearchDAO.findDescendants(SyncopeConstants.ROOT_REALM, SyncopeConstants.ROOT_REALM);
+ List<Realm> found = realmSearchDAO.findDescendants(SyncopeConstants.ROOT_REALM, SyncopeConstants.ROOT_REALM);
assertEquals(4, found.size());
- assertTrue(found.stream().allMatch(f -> SyncopeConstants.UUID_PATTERN.matcher(f).matches()));
+ assertTrue(found.stream().allMatch(f -> SyncopeConstants.UUID_PATTERN.matcher(f.getKey()).matches()));
- assertEquals(
- found,
- realmSearchDAO.findDescendants(SyncopeConstants.ROOT_REALM, null, Pageable.unpaged()).stream().
- map(Realm::getKey).toList());
+ assertEquals(found, realmSearchDAO.findDescendants(SyncopeConstants.ROOT_REALM, null));
}
@Test
@@ -110,14 +107,14 @@ public class RealmTest extends AbstractTest {
@Test
public void findAll() {
- List<Realm> list = realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null, Pageable.unpaged());
+ List<Realm> list = realmSearchDAO.findDescendants(realmDAO.getRoot().getFullPath(), null);
assertNotNull(list);
assertFalse(list.isEmpty());
list.forEach(Assertions::assertNotNull);
assertEquals(4, realmDAO.findAll(Pageable.ofSize(100)).stream().count());
- list = realmSearchDAO.findDescendants(Set.of("/even", "/odd"), null, Pageable.unpaged());
+ list = realmSearchDAO.search(Set.of("/even", "/odd"), realmSearchDAO.getAllMatchingCond(), Pageable.unpaged());
assertEquals(3, list.size());
assertNotNull(list.stream().filter(realm -> "even".equals(realm.getName())).findFirst().orElseThrow());
assertNotNull(list.stream().filter(realm -> "two".equals(realm.getName())).findFirst().orElseThrow());
diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java
index 8286c5f2b7..b49c015746 100644
--- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java
+++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/inner/UserTest.java
@@ -30,7 +30,6 @@ import java.time.OffsetDateTime;
import java.util.List;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.core.persistence.api.EncryptorManager;
-import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
@@ -61,9 +60,6 @@ public class UserTest extends AbstractTest {
@Autowired
private ExternalResourceDAO resourceDAO;
- @Autowired
- private DerSchemaDAO derSchemaDAO;
-
@Autowired
private SecurityQuestionDAO securityQuestionDAO;
@@ -109,33 +105,6 @@ public class UserTest extends AbstractTest {
assertEquals(5, count);
}
- @Test
- public void findByDerAttrValue() {
- List<User> list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "Vivaldi, Antonio", false);
- assertEquals(1, list.size());
-
- list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", false);
- assertEquals(0, list.size());
-
- list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "VIVALDI, ANTONIO", true);
- assertEquals(1, list.size());
- }
-
- @Test
- public void findByInvalidDerAttrValue() {
- assertTrue(userDAO.findByDerAttrValue(
- derSchemaDAO.findById("cn").orElseThrow().getExpression(), "Antonio, Maria, Rossi", false).isEmpty());
- }
-
- @Test
- public void findByInvalidDerAttrExpression() {
- assertThrows(IllegalArgumentException.class, () -> userDAO.findByDerAttrValue(
- derSchemaDAO.findById("noschema").orElseThrow().getExpression(), "Antonio, Maria", false).isEmpty());
- }
-
@Test
public void findByKey() {
assertTrue(userDAO.findById("1417acbe-cbf6-4277-9372-e75e04f97000").isPresent());
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 96ec720672..a89ebc2523 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
@@ -33,6 +33,7 @@ import org.apache.syncope.common.lib.types.ClientExceptionType;
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.DerSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
@@ -41,6 +42,7 @@ 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.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.DerSchema;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
@@ -76,6 +78,9 @@ public class AnySearchTest extends AbstractTest {
@Autowired
private RealmSearchDAO realmSearchDAO;
+ @Autowired
+ private DerSchemaDAO derSchemaDAO;
+
@Autowired
private PlainAttrValidationManager validator;
@@ -182,6 +187,41 @@ public class AnySearchTest extends AbstractTest {
assertEquals("c9b2dec2-00a7-4855-97c0-d854842b4b24", users.getFirst().getKey());
}
+ @Test
+ public void issueSYNCOPE800() {
+ // create derived attribute (literal as prefix)
+ DerSchema prefix = entityFactory.newEntity(DerSchema.class);
+ prefix.setKey("kprefix");
+ prefix.setExpression("'k' + firstname");
+
+ derSchemaDAO.save(prefix);
+
+ // create derived attribute (literal as suffix)
+ DerSchema suffix = entityFactory.newEntity(DerSchema.class);
+ suffix.setKey("ksuffix");
+ suffix.setExpression("firstname + 'k'");
+
+ derSchemaDAO.save(suffix);
+
+ // add derived attributes to user
+ User owner = userDAO.findByUsername("vivaldi").orElseThrow();
+
+ String firstname = owner.getPlainAttr("firstname").get().getValuesAsStrings().getFirst();
+ assertNotNull(firstname);
+
+ // search by ksuffix derived attribute
+ List<User> list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("ksuffix").orElseThrow().getExpression(),
+ firstname + 'k', false, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+
+ // search by kprefix derived attribute
+ list = searchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("kprefix").orElseThrow().getExpression(),
+ 'k' + firstname, false, AnyTypeKind.USER);
+ assertEquals(1, list.size());
+ }
+
@Test
public void issueSYNCOPE1417() {
AnyCond usernameLeafCond = new AnyCond(AnyCond.Type.EQ);
diff --git a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/RealmTest.java b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/RealmTest.java
index 941889f3cb..cd457b900b 100644
--- a/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/RealmTest.java
+++ b/core/persistence-neo4j/src/test/java/org/apache/syncope/core/persistence/neo4j/outer/RealmTest.java
@@ -18,10 +18,15 @@
*/
package org.apache.syncope.core.persistence.neo4j.outer;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.util.List;
+import java.util.Set;
import java.util.UUID;
+import org.apache.syncope.common.lib.SyncopeConstants;
import org.apache.syncope.common.lib.types.IdRepoImplementationType;
import org.apache.syncope.common.lib.types.ImplementationEngine;
import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
@@ -31,6 +36,9 @@ import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
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.search.AnyCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.PlainAttr;
import org.apache.syncope.core.persistence.api.entity.Realm;
@@ -39,6 +47,8 @@ import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.neo4j.AbstractTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@@ -136,4 +146,120 @@ public class RealmTest extends AbstractTest {
realm = realmDAO.findById("722f3d84-9c2b-4525-8f6e-e4b82c55a36c").orElseThrow();
assertTrue(realm.getActions().isEmpty());
}
+
+ @Test
+ public void search() {
+ Realm two = realmSearchDAO.findByFullPath("/even/two").orElseThrow();
+ two.add(anyTypeClassDAO.findById("other").orElseThrow());
+ two = realmDAO.save(two);
+
+ two = realmDAO.findById(two.getKey()).orElseThrow();
+ PlainAttr aLong = new PlainAttr();
+ aLong.setSchema("aLong");
+ aLong.add(validator, "42");
+ two.add(aLong);
+ two = realmDAO.save(two);
+
+ Realm odd = realmSearchDAO.findByFullPath("/odd").orElseThrow();
+ odd.add(anyTypeClassDAO.findById("other").orElseThrow());
+ odd = realmDAO.save(odd);
+
+ odd = realmDAO.findById(odd.getKey()).orElseThrow();
+ PlainAttr oddLong = new PlainAttr();
+ oddLong.setSchema("aLong");
+ oddLong.add(validator, "99");
+ odd.add(oddLong);
+ realmDAO.save(odd);
+
+ two = realmDAO.findById(two.getKey()).orElseThrow();
+ assertEquals(anyTypeClassDAO.findById("other").orElseThrow(), two.getAnyTypeClasses().iterator().next());
+ assertEquals(1, two.getPlainAttrs().size());
+ assertEquals(42, two.getPlainAttr("aLong").orElseThrow().getValues().getFirst().getLongValue());
+
+ odd = realmDAO.findById(odd.getKey()).orElseThrow();
+ assertEquals(anyTypeClassDAO.findById("other").orElseThrow(), odd.getAnyTypeClasses().iterator().next());
+ assertEquals(1, odd.getPlainAttrs().size());
+ assertEquals(99, odd.getPlainAttr("aLong").orElseThrow().getValues().getFirst().getLongValue());
+
+ AnyCond name = new AnyCond(AttrCond.Type.EQ);
+ name.setSchema("name");
+ name.setExpression("two");
+
+ List<Realm> result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(name),
+ Pageable.unpaged());
+ assertEquals(1, result.size());
+ assertEquals("two", result.getFirst().getName());
+
+ AttrCond attrEq = new AttrCond(AttrCond.Type.EQ);
+ attrEq.setSchema("aLong");
+ attrEq.setExpression("42");
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrEq),
+ Pageable.unpaged());
+ assertEquals(1, result.size());
+ assertEquals("two", result.getFirst().getName());
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.and(SearchCond.of(name), SearchCond.of(attrEq)),
+ Pageable.unpaged());
+ assertEquals(1, result.size());
+ assertEquals("two", result.getFirst().getName());
+
+ AttrCond attrEq2 = new AttrCond(AttrCond.Type.EQ);
+ attrEq2.setSchema("aLong");
+ attrEq2.setExpression("99");
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.or(SearchCond.of(attrEq), SearchCond.of(attrEq2)),
+ Pageable.unpaged());
+ assertTrue(result.stream().anyMatch(r -> "two".equals(r.getName())));
+ assertTrue(result.stream().anyMatch(r -> "odd".equals(r.getName())));
+
+ AttrCond attrIsNull = new AttrCond(AttrCond.Type.ISNULL);
+ attrIsNull.setSchema("aLong");
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNull),
+ Pageable.unpaged());
+ result.forEach(r -> assertFalse("two".equals(r.getName()) || "odd".equals(r.getName())));
+
+ AttrCond attrIsNotNull = new AttrCond(AttrCond.Type.ISNOTNULL);
+ attrIsNotNull.setSchema("aLong");
+
+ assertEquals(2, realmSearchDAO.count(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNotNull)));
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNotNull),
+ Pageable.unpaged(Sort.by(new Sort.Order(Sort.Direction.ASC, "name"))));
+ assertEquals("odd", result.get(0).getName());
+ assertEquals("two", result.get(1).getName());
+
+ result = realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.of(attrIsNotNull),
+ Pageable.unpaged(Sort.by(new Sort.Order(Sort.Direction.ASC, "aLong"))));
+ assertEquals("two", result.get(0).getName());
+ assertEquals("odd", result.get(1).getName());
+
+ AttrCond cond1 = new AttrCond(AttrCond.Type.EQ);
+ cond1.setSchema("aLong");
+ cond1.setExpression("42");
+ AttrCond cond2 = new AttrCond(AttrCond.Type.IEQ);
+ cond2.setSchema("ctype");
+ cond2.setExpression("string");
+ assertDoesNotThrow(() -> realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.and(SearchCond.of(cond1), SearchCond.of(cond2)),
+ Pageable.unpaged()));
+ }
}
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 1de4247081..5c8fed9aed 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
@@ -28,10 +28,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.core.persistence.api.attrvalue.InvalidEntityException;
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.AnyTypeClassDAO;
import org.apache.syncope.core.persistence.api.dao.DelegationDAO;
import org.apache.syncope.core.persistence.api.dao.DerSchemaDAO;
@@ -94,6 +96,9 @@ public class UserTest extends AbstractTest {
@Autowired
private AnyTypeClassDAO anyTypeClassDAO;
+ @Autowired
+ private AnySearchDAO anySearchDAO;
+
@Autowired
private PlainAttrValidationManager validator;
@@ -285,13 +290,15 @@ public class UserTest extends AbstractTest {
assertNotNull(firstname);
// search by ksuffix derived attribute
- List<User> list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("ksuffix").orElseThrow().getExpression(), firstname + 'k', false);
+ List<User> list = anySearchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("ksuffix").orElseThrow().getExpression(),
+ firstname + 'k', false, AnyTypeKind.USER);
assertEquals(1, list.size());
// search by kprefix derived attribute
- list = userDAO.findByDerAttrValue(
- derSchemaDAO.findById("kprefix").orElseThrow().getExpression(), 'k' + firstname, false);
+ list = anySearchDAO.findByDerAttrValue(
+ derSchemaDAO.findById("kprefix").orElseThrow().getExpression(),
+ 'k' + firstname, false, AnyTypeKind.USER);
assertEquals(1, list.size());
}
}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
index 4d7a6849d6..1458cfb449 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ProvisioningContext.java
@@ -64,7 +64,7 @@ import org.apache.syncope.core.persistence.api.dao.WAConfigDAO;
import org.apache.syncope.core.persistence.api.entity.AnyUtilsFactory;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.task.TaskUtilsFactory;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.persistence.api.utils.RealmUtils;
import org.apache.syncope.core.provisioning.api.AnyObjectProvisioningManager;
import org.apache.syncope.core.provisioning.api.AuditEventProcessor;
@@ -581,7 +581,7 @@ public class ProvisioningContext {
@Bean
public NotificationManager notificationManager(
final EntityFactory entityFactory,
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final DerSchemaDAO derSchemaDAO,
final NotificationDAO notificationDAO,
final AnyObjectDAO anyObjectDAO,
@@ -857,7 +857,7 @@ public class ProvisioningContext {
@ConditionalOnMissingBean
@Bean
public FIQLQueryDataBinder fiqlQueryDataBinder(
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final UserDAO userDAO,
final EntityFactory entityFactory) {
@@ -868,7 +868,7 @@ public class ProvisioningContext {
@Bean
public GroupDataBinder groupDataBinder(
final EntityFactory entityFactory,
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final AnyUtilsFactory anyUtilsFactory,
final AnyTypeDAO anyTypeDAO,
final RealmSearchDAO realmSearchDAO,
@@ -1044,15 +1044,10 @@ public class ProvisioningContext {
@Bean
public RoleDataBinder roleDataBinder(
final EntityFactory entityFactory,
- final SearchCondVisitor searchCondVisitor,
final RealmSearchDAO realmSearchDAO,
final RoleDAO roleDAO) {
- return new RoleDataBinderImpl(
- realmSearchDAO,
- roleDAO,
- entityFactory,
- searchCondVisitor);
+ return new RoleDataBinderImpl(realmSearchDAO, roleDAO, entityFactory);
}
@ConditionalOnMissingBean
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/FIQLQueryDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/FIQLQueryDataBinderImpl.java
index 61ba15b93f..748d0008e2 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/FIQLQueryDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/FIQLQueryDataBinderImpl.java
@@ -27,8 +27,8 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.FIQLQuery;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.provisioning.api.data.FIQLQueryDataBinder;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.slf4j.Logger;
@@ -38,14 +38,14 @@ public class FIQLQueryDataBinderImpl implements FIQLQueryDataBinder {
protected static final Logger LOG = LoggerFactory.getLogger(FIQLQueryDataBinder.class);
- protected final SearchCondVisitor searchCondVisitor;
+ protected final AnySearchCondVisitor searchCondVisitor;
protected final UserDAO userDAO;
protected final EntityFactory entityFactory;
public FIQLQueryDataBinderImpl(
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final UserDAO userDAO,
final EntityFactory entityFactory) {
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 a13e93d8d0..86fb42b166 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
@@ -54,7 +54,7 @@ import org.apache.syncope.core.persistence.api.entity.Groupable;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.group.Group;
import org.apache.syncope.core.persistence.api.entity.group.GroupTypeExtension;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.provisioning.api.DerAttrHandler;
import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
import org.apache.syncope.core.provisioning.api.MappingManager;
@@ -68,7 +68,7 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(rollbackFor = { Throwable.class })
public class GroupDataBinderImpl extends AnyDataBinder implements GroupDataBinder {
- protected final SearchCondVisitor searchCondVisitor;
+ protected final AnySearchCondVisitor searchCondVisitor;
public GroupDataBinderImpl(
final AnyTypeDAO anyTypeDAO,
@@ -87,7 +87,7 @@ public class GroupDataBinderImpl extends AnyDataBinder implements GroupDataBinde
final MappingManager mappingManager,
final IntAttrNameParser intAttrNameParser,
final OutboundMatcher outboundMatcher,
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final PlainAttrValidationManager validator,
final JexlTools jexlTools) {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RoleDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RoleDataBinderImpl.java
index 42eacc4f52..2ba900ef10 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RoleDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RoleDataBinderImpl.java
@@ -24,7 +24,6 @@ import org.apache.syncope.core.persistence.api.dao.RoleDAO;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.Role;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.provisioning.api.data.RoleDataBinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,18 +38,14 @@ public class RoleDataBinderImpl implements RoleDataBinder {
protected final EntityFactory entityFactory;
- protected final SearchCondVisitor searchCondVisitor;
-
public RoleDataBinderImpl(
final RealmSearchDAO realmSearchDAO,
final RoleDAO roleDAO,
- final EntityFactory entityFactory,
- final SearchCondVisitor searchCondVisitor) {
+ final EntityFactory entityFactory) {
this.realmSearchDAO = realmSearchDAO;
this.roleDAO = roleDAO;
this.entityFactory = entityFactory;
- this.searchCondVisitor = searchCondVisitor;
}
@Override
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
index cbf6229eb2..8f50156872 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/notification/DefaultNotificationManager.java
@@ -64,8 +64,8 @@ import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
import org.apache.syncope.core.persistence.api.entity.user.UMembership;
import org.apache.syncope.core.persistence.api.entity.user.URelationship;
import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.provisioning.api.DerAttrHandler;
import org.apache.syncope.core.provisioning.api.IntAttrName;
import org.apache.syncope.core.provisioning.api.IntAttrNameParser;
@@ -119,7 +119,7 @@ public class DefaultNotificationManager implements NotificationManager {
protected final IntAttrNameParser intAttrNameParser;
- protected final SearchCondVisitor searchCondVisitor;
+ protected final AnySearchCondVisitor searchCondVisitor;
protected final JexlTools jexlTools;
@@ -142,7 +142,7 @@ public class DefaultNotificationManager implements NotificationManager {
final ConfParamOps confParamOps,
final EntityFactory entityFactory,
final IntAttrNameParser intAttrNameParser,
- final SearchCondVisitor searchCondVisitor,
+ final AnySearchCondVisitor searchCondVisitor,
final JexlTools jexlTools) {
this.derSchemaDAO = derSchemaDAO;
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 c317631790..a5f6930f25 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
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
@@ -143,11 +144,11 @@ public class InboundMatcher {
// first, attempt to match the provided connObjectLinkValue against the configured connObjectLink
// (if available) via internal search
if (StringUtils.isNotBlank(provision.getMapping().getConnObjectLink())) {
- List<? extends Any> found = anyUtilsFactory.getInstance(anyType.getKind()).dao().
- findByDerAttrValue(
- provision.getMapping().getConnObjectLink(),
- connObjectLinkValue,
- provision.isIgnoreCaseMatch());
+ List<? extends Any> found = anySearchDAO.findByDerAttrValue(
+ provision.getMapping().getConnObjectLink(),
+ connObjectLinkValue,
+ provision.isIgnoreCaseMatch(),
+ anyType.getKind());
if (!found.isEmpty()) {
return Optional.of(new InboundMatch(MatchType.ANY, found.getFirst()));
}
@@ -308,10 +309,11 @@ public class InboundMatcher {
}
case DERIVED ->
- anys.addAll(anyUtils.dao().findByDerAttrValue(
+ anys.addAll(anySearchDAO.findByDerAttrValue(
((DerSchema) intAttrName.getSchemaInfo().schema()).getExpression(),
finalConnObjectKeyValue,
- ignoreCaseMatch));
+ ignoreCaseMatch,
+ anyTypeKind));
default -> {
}
@@ -439,10 +441,9 @@ public class InboundMatcher {
public List<Realm> match(final LiveSyncDelta syncDelta, final OrgUnit orgUnit) {
String connObjectKey = null;
- Optional<Item> connObjectKeyItem = orgUnit.getConnObjectKeyItem();
- if (connObjectKeyItem.isPresent()) {
- Attribute connObjectKeyAttr = syncDelta.getObject().
- getAttributeByName(connObjectKeyItem.get().getExtAttrName());
+ Item connObjectKeyItem = orgUnit.getConnObjectKeyItem().orElse(null);
+ if (connObjectKeyItem != null) {
+ Attribute connObjectKeyAttr = syncDelta.getObject().getAttributeByName(connObjectKeyItem.getExtAttrName());
if (connObjectKeyAttr != null) {
connObjectKey = AttributeUtil.getStringValue(connObjectKeyAttr);
}
@@ -452,10 +453,10 @@ public class InboundMatcher {
}
for (ItemTransformer transformer
- : MappingUtils.getItemTransformers(connObjectKeyItem.get(), getTransformers(connObjectKeyItem.get()))) {
+ : MappingUtils.getItemTransformers(connObjectKeyItem, getTransformers(connObjectKeyItem))) {
List<Object> output = transformer.beforePull(
- connObjectKeyItem.get(),
+ connObjectKeyItem,
null,
List.of(connObjectKey));
if (!CollectionUtils.isEmpty(output)) {
@@ -463,27 +464,58 @@ public class InboundMatcher {
}
}
+ IntAttrName intAttrName;
+ try {
+ intAttrName = intAttrNameParser.parse(connObjectKeyItem.getIntAttrName());
+ } catch (ParseException e) {
+ LOG.error("Invalid intAttrName '{}' specified, ignoring", connObjectKeyItem.getIntAttrName(), e);
+ return List.of();
+ }
+
List<Realm> result = new ArrayList<>();
- switch (connObjectKeyItem.get().getIntAttrName()) {
- case "key" -> {
- realmDAO.findById(connObjectKey).ifPresent(result::add);
- }
+ if (intAttrName.getField() != null) {
+ switch (intAttrName.getField()) {
+ case "key" ->
+ realmDAO.findById(connObjectKey).ifPresent(result::add);
- case "name" -> {
- if (orgUnit.isIgnoreCaseMatch()) {
- result.addAll(realmSearchDAO.findDescendants(
- SyncopeConstants.ROOT_REALM, connObjectKey, Pageable.unpaged()));
- } else {
- result.addAll(realmSearchDAO.findByName(connObjectKey).stream().toList());
+ case "name" -> {
+ if (orgUnit.isIgnoreCaseMatch()) {
+ AnyCond cond = new AnyCond();
+ cond.setType(AttrCond.Type.IEQ);
+ cond.setSchema("name");
+ cond.setExpression(connObjectKey);
+ result.addAll(realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM), SearchCond.of(cond), Pageable.unpaged()));
+ } else {
+ result.addAll(realmSearchDAO.findByName(connObjectKey).stream().toList());
+ }
}
- }
- case "fullpath" -> {
- realmSearchDAO.findByFullPath(connObjectKey).ifPresent(result::add);
+ case "fullpath" ->
+ realmSearchDAO.findByFullPath(connObjectKey).ifPresent(result::add);
+
+ default -> {
+ }
}
+ } else if (intAttrName.getSchemaInfo() != null) {
+ switch (intAttrName.getSchemaInfo().type()) {
+ case PLAIN -> {
+ AttrCond cond = new AttrCond(orgUnit.isIgnoreCaseMatch() ? AttrCond.Type.IEQ : AttrCond.Type.EQ);
+ cond.setSchema(intAttrName.getSchemaInfo().schema().getKey());
+ cond.setExpression(connObjectKey);
+ result.addAll(realmSearchDAO.search(
+ Set.of(SyncopeConstants.ROOT_REALM), SearchCond.of(cond), Pageable.unpaged()));
+ }
+
+ case DERIVED ->
+ result.addAll(realmSearchDAO.findByDerAttrValue(
+ ((DerSchema) intAttrName.getSchemaInfo().schema()).getExpression(),
+ connObjectKey,
+ orgUnit.isIgnoreCaseMatch()));
- default -> {
+ default -> {
+ }
}
}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
index a5548eadb4..fd2613e37a 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/PushJobDelegate.java
@@ -42,8 +42,8 @@ import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.api.entity.Realm;
import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
import org.apache.syncope.core.persistence.api.entity.task.PushTask;
+import org.apache.syncope.core.persistence.api.search.AnySearchCondVisitor;
import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
-import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
import org.apache.syncope.core.provisioning.api.ProvisionSorter;
import org.apache.syncope.core.provisioning.api.job.JobExecutionContext;
import org.apache.syncope.core.provisioning.api.job.JobExecutionException;
@@ -59,7 +59,6 @@ import org.apache.syncope.core.spring.implementation.ImplementationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Pageable;
public class PushJobDelegate
extends AbstractProvisioningJobDelegate<PushTask>
@@ -72,7 +71,7 @@ public class PushJobDelegate
protected RealmSearchDAO realmSearchDAO;
@Autowired
- protected SearchCondVisitor searchCondVisitor;
+ protected AnySearchCondVisitor searchCondVisitor;
@Autowired
protected ConfigurableApplicationContext ctx;
@@ -212,7 +211,7 @@ public class PushJobDelegate
// Never push the root realm
List<Realm> realms = realmSearchDAO.findDescendants(
- profile.getTask().getSourceRealm().getFullPath(), null, Pageable.unpaged()).stream().
+ profile.getTask().getSourceRealm().getFullPath(), null).stream().
filter(realm -> realm.getParent() != null).toList();
boolean result = true;
for (int i = 0; i < realms.size() && result; i++) {
@@ -238,8 +237,6 @@ public class PushJobDelegate
AnyType anyType = anyTypeDAO.findById(provision.getAnyType()).
orElseThrow(() -> new NotFoundException("AnyType" + provision.getAnyType()));
- AnyDAO<?> anyDAO = anyUtilsFactory.getInstance(anyType.getKind()).dao();
-
dispatcher.addHandlerSupplier(provision.getAnyType(), () -> {
SyncopePushResultHandler handler;
switch (anyType.getKind()) {
@@ -261,7 +258,7 @@ public class PushJobDelegate
String filter = task.getFilter(anyType.getKey()).orElse(null);
SearchCond cond = StringUtils.isBlank(filter)
- ? anyDAO.getAllMatchingCond()
+ ? searchDAO.getAllMatchingCond()
: SearchCondConverter.convert(searchCondVisitor, filter);
long count = searchDAO.count(
profile.getTask().getSourceRealm(),
diff --git a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
index 2aa43a642e..fed1700b25 100644
--- a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
+++ b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/starter/SelfKeymasterContext.java
@@ -180,7 +180,7 @@ public class SelfKeymasterContext {
ExternalDocumentation extDoc = new ExternalDocumentation();
extDoc.setDescription("Apache Syncope Reference Guide");
- extDoc.setUrl("https://syncope.apache.org/docs/3.0/reference-guide.html#domains");
+ extDoc.setUrl("https://syncope.apache.org/docs/4.0/reference-guide.html#domains");
Schema<String> schema = new Schema<>();
schema.setDescription("Domains are built to facilitate multitenancy.");
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 2018ac04b6..146abe8f0f 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
@@ -90,6 +90,17 @@ public class ElasticsearchUtils {
builder.put("relationshipTypes", relationshipTypes);
}
+ protected void addPlainAttr(final Map<String, Object> builder, final List<PlainAttr> plainAttrs) {
+ for (PlainAttr plainAttr : plainAttrs) {
+ List<Object> values = plainAttr.getValues().stream().
+ map(PlainAttrValue::getValue).collect(Collectors.toList());
+
+ Optional.ofNullable(plainAttr.getUniqueValue()).ifPresent(v -> values.add(v.getValue()));
+
+ builder.put(plainAttr.getSchema(), values.size() == 1 ? values.getFirst() : values);
+ }
+ }
+
/**
* Returns the document specialized with content from the provided any.
*
@@ -165,14 +176,7 @@ public class ElasticsearchUtils {
}
}
- for (PlainAttr plainAttr : any.getPlainAttrs()) {
- List<Object> values = plainAttr.getValues().stream().
- map(PlainAttrValue::getValue).collect(Collectors.toList());
-
- Optional.ofNullable(plainAttr.getUniqueValue()).ifPresent(v -> values.add(v.getValue()));
-
- builder.put(plainAttr.getSchema(), values.size() == 1 ? values.getFirst() : values);
- }
+ addPlainAttr(builder, any.getPlainAttrs());
// add also flattened membership attributes
if (any instanceof Groupable<?, ?, ?> groupable) {
@@ -210,6 +214,7 @@ public class ElasticsearchUtils {
builder.put("name", realm.getName());
builder.put("parent_id", realm.getParent() == null ? null : realm.getParent().getKey());
builder.put("fullPath", realm.getFullPath());
+ addPlainAttr(builder, realm.getPlainAttrs());
customizeDocument(builder, realm);
@@ -235,8 +240,6 @@ public class ElasticsearchUtils {
return builder;
}
- protected void customizeDocument(
- final Map<String, Object> builder,
- final AuditEvent auditEvent) {
+ protected void customizeDocument(final Map<String, Object> builder, final AuditEvent auditEvent) {
}
}
diff --git a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/ElasticsearchPersistenceContext.java b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/ElasticsearchPersistenceContext.java
index cd95e10699..24aa529631 100644
--- a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/ElasticsearchPersistenceContext.java
+++ b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/ElasticsearchPersistenceContext.java
@@ -74,10 +74,23 @@ public class ElasticsearchPersistenceContext {
@Bean
public RealmSearchDAO realmSearchDAO(
final @Lazy RealmDAO realmDAO,
+ final @Lazy PlainSchemaDAO plainSchemaDAO,
+ final @Lazy UserDAO userDAO,
+ final @Lazy GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
final ElasticsearchClient client,
final ElasticsearchProperties props) {
- return new ElasticsearchRealmSearchDAO(realmDAO, client, props.getIndexMaxResultWindow());
+ return new ElasticsearchRealmSearchDAO(
+ realmDAO,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ client,
+ props.getIndexMaxResultWindow());
}
@ConditionalOnMissingBean(name = "elasticsearchAuditEventDAO")
diff --git a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java
index 2e3ba71b3f..986176cc8b 100644
--- a/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java
+++ b/ext/elasticsearch/persistence/src/main/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAO.java
@@ -143,7 +143,7 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
});
realmSearchDAO.findDescendants(realm.getFullPath(), base.getFullPath()).
- forEach(descendant -> queries.add(
+ stream().map(Realm::getKey).forEach(descendant -> queries.add(
new Query.Builder().term(QueryBuilders.term().
field("realm").value(descendant).caseInsensitive(false).build()).
build()));
@@ -570,8 +570,8 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
}
@Override
- protected CheckResult<AnyCond> check(final AnyCond cond, final AnyTypeKind kind) {
- CheckResult<AnyCond> checked = super.check(cond, kind);
+ protected CheckResult<AnyCond> check(final AnyCond cond, final Field field, final Set<String> relationshipsFields) {
+ CheckResult<AnyCond> checked = super.check(cond, field, relationshipsFields);
// Manage difference between external id attribute and internal _id
if ("id".equals(checked.cond().getSchema())) {
@@ -591,7 +591,11 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
cond.setExpression(realm.getKey());
}
- CheckResult<AnyCond> checked = check(cond, kind);
+ CheckResult<AnyCond> checked = check(
+ cond,
+ anyUtilsFactory.getInstance(kind).getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
return fillAttrQuery(checked.schema(), checked.value(), checked.cond());
}
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 df85297a61..395a475646 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
@@ -19,40 +19,65 @@
package org.apache.syncope.core.persistence.elasticsearch.dao;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.FieldSort;
+import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.ScriptLanguage;
import co.elastic.clients.elasticsearch._types.ScriptSortType;
import co.elastic.clients.elasticsearch._types.ScriptSource;
import co.elastic.clients.elasticsearch._types.SearchType;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
+import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
+import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch.core.CountRequest;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.SourceConfig;
+import co.elastic.clients.json.JsonData;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
+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.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+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.Realm;
+import org.apache.syncope.core.persistence.api.utils.FormatUtils;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
+import org.apache.syncope.core.persistence.common.dao.AbstractRealmSearchDAO;
+import org.apache.syncope.core.persistence.common.dao.AbstractSearchDAO.CheckResult;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.ext.elasticsearch.client.ElasticsearchUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
-public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
+public class ElasticsearchRealmSearchDAO extends AbstractRealmSearchDAO implements RealmSearchDAO {
- protected static final Logger LOG = LoggerFactory.getLogger(RealmDAO.class);
+ protected static final Logger LOG = LoggerFactory.getLogger(RealmSearchDAO.class);
- protected static final List<SortOptions> REALM_SORT_OPTIONS = List.of(
+ protected static final Set<String> ID_PROPS = Set.of("key", "id", "_id");
+
+ protected static final List<SortOptions> FULLPATH_SORT_OPTIONS = List.of(
new SortOptions.Builder().
script(s -> s.type(ScriptSortType.Number).
script(t -> t.lang(ScriptLanguage.Painless).
@@ -63,16 +88,25 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
protected final RealmDAO realmDAO;
+ protected final RealmUtils realmUtils;
+
protected final ElasticsearchClient client;
protected final int indexMaxResultWindow;
public ElasticsearchRealmSearchDAO(
final RealmDAO realmDAO,
+ final PlainSchemaDAO plainSchemaDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
final ElasticsearchClient client,
final int indexMaxResultWindow) {
+ super(plainSchemaDAO, userDAO, groupDAO, entityFactory, validator);
this.realmDAO = realmDAO;
+ this.realmUtils = new RealmUtils(entityFactory);
this.client = client;
this.indexMaxResultWindow = indexMaxResultWindow;
}
@@ -113,7 +147,7 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
searchType(SearchType.QueryThenFetch).
query(query).
- sort(REALM_SORT_OPTIONS).
+ sort(FULLPATH_SORT_OPTIONS).
fields(List.of()).source(new SourceConfig.Builder().fetch(false).build()).
build();
LOG.debug("Search request: {}", request);
@@ -146,7 +180,7 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
flatMap(Optional::stream).map(Realm.class::cast).toList();
}
- protected Query buildDescendantsQuery(final Set<String> bases, final String keyword) {
+ protected Query buildDescendantsQuery(final Set<String> bases, final SearchCond searchCond) {
List<Query> basesQueries = new ArrayList<>();
bases.forEach(base -> {
basesQueries.add(new Query.Builder().term(QueryBuilders.term().
@@ -157,28 +191,199 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
});
Query prefix = new Query.Builder().disMax(QueryBuilders.disMax().queries(basesQueries).build()).build();
- if (keyword == null) {
- return prefix;
+ BoolQuery.Builder boolBuilder = QueryBuilders.bool().filter(prefix);
+ if (searchCond != null) {
+ boolBuilder.filter(getQuery(searchCond));
}
+ return new Query.Builder().bool(boolBuilder.build()).build();
+ }
- return new Query.Builder().bool(QueryBuilders.bool().filter(
- prefix,
- new Query.Builder().wildcard(QueryBuilders.wildcard().
- field("name").value(keyword.replace('%', '*').replace("\\_", "_")).
- caseInsensitive(true).build()).build()).build()).
- build();
+ protected Query getQuery(final SearchCond cond) {
+ Query query = null;
+
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ query = cond.asLeaf(AnyCond.class).map(this::getQuery).orElse(null);
+ if (query == null) {
+ query = cond.asLeaf(AttrCond.class).map(this::getQuery).orElse(null);
+ }
+ if (query == null) {
+ query = getQueryForCustomConds(cond);
+ }
+ if (query == null) {
+ throw new IllegalArgumentException("Cannot construct QueryBuilder");
+ }
+ if (cond.getType() == SearchCond.Type.NOT_LEAF) {
+ query = new Query.Builder().bool(QueryBuilders.bool().mustNot(query).build()).build();
+ }
+ }
+ case AND -> {
+ List<Query> andCompound = new ArrayList<>();
+ Query andLeft = getQuery(cond.getLeft());
+ if (andLeft.isBool() && !andLeft.bool().filter().isEmpty()) {
+ andCompound.addAll(andLeft.bool().filter());
+ } else {
+ andCompound.add(andLeft);
+ }
+ Query andRight = getQuery(cond.getRight());
+ if (andRight.isBool() && !andRight.bool().filter().isEmpty()) {
+ andCompound.addAll(andRight.bool().filter());
+ } else {
+ andCompound.add(andRight);
+ }
+ query = new Query.Builder().bool(QueryBuilders.bool().filter(andCompound).build()).build();
+ }
+ case OR -> {
+ List<Query> orCompound = new ArrayList<>();
+ Query orLeft = getQuery(cond.getLeft());
+ if (orLeft.isDisMax()) {
+ orCompound.addAll(orLeft.disMax().queries());
+ } else {
+ orCompound.add(orLeft);
+ }
+ Query orRight = getQuery(cond.getRight());
+ if (orRight.isDisMax()) {
+ orCompound.addAll(orRight.disMax().queries());
+ } else {
+ orCompound.add(orRight);
+ }
+ query = new Query.Builder().disMax(QueryBuilders.disMax().queries(orCompound).build()).build();
+ }
+ default -> {
+ }
+ }
+
+ return query;
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ protected CheckResult<AnyCond> check(final AnyCond cond, final Field field, final Set<String> relationshipsFields) {
+ CheckResult<AnyCond> checked = super.check(cond, field, relationshipsFields);
+
+ // Manage difference between external id attribute and internal _id
+ if ("id".equals(checked.cond().getSchema())) {
+ checked.cond().setSchema("_id");
+ }
+ if ("id".equals(checked.schema().getKey())) {
+ checked.schema().setKey("_id");
+ }
+
+ return checked;
+ }
+
+ protected Query fillAttrQuery(
+ final PlainSchema schema,
+ final PlainAttrValue attrValue,
+ final AttrCond cond) {
+
+ Object value = schema.getType() == AttrSchemaType.Date && attrValue.getDateValue() != null
+ ? FormatUtils.format(attrValue.getDateValue())
+ : attrValue.getValue();
+
+ Query query = null;
+ switch (cond.getType()) {
+ case ISNOTNULL:
+ query = new Query.Builder().exists(QueryBuilders.exists().field(schema.getKey()).build()).build();
+ break;
+
+ case ISNULL:
+ query = new Query.Builder().bool(QueryBuilders.bool().mustNot(
+ new Query.Builder().exists(QueryBuilders.exists().field(schema.getKey()).build())
+ .build()).build()).build();
+ break;
+
+ case ILIKE:
+ query = new Query.Builder().wildcard(QueryBuilders.wildcard().
+ field(schema.getKey()).value(cond.getExpression().replace('%', '*').replace("\\_", "_")).
+ caseInsensitive(true).build()).build();
+ break;
+
+ case LIKE:
+ query = new Query.Builder().wildcard(QueryBuilders.wildcard().
+ field(schema.getKey()).value(cond.getExpression().replace('%', '*').replace("\\_", "_")).
+ caseInsensitive(false).build()).build();
+ break;
+
+ case IEQ:
+ query = new Query.Builder().term(QueryBuilders.term().
+ field(schema.getKey()).value(FieldValue.of(cond.getExpression())).caseInsensitive(true).
+ build()).build();
+ break;
+
+ case EQ:
+ FieldValue fieldValue = switch (value) {
+ case Double aDouble ->
+ FieldValue.of(aDouble);
+ case Long aLong ->
+ FieldValue.of(aLong);
+ case Boolean aBoolean ->
+ FieldValue.of(aBoolean);
+ default ->
+ FieldValue.of(value.toString());
+ };
+ query = new Query.Builder().term(QueryBuilders.term().
+ field(schema.getKey()).value(fieldValue).caseInsensitive(false).build()).
+ build();
+ break;
+
+ case GE:
+ query = new Query.Builder().range(RangeQuery.of(r -> r.untyped(n -> n.
+ field(schema.getKey()).
+ gte(JsonData.of(value))))).
+ build();
+ break;
+
+ case GT:
+ query = new Query.Builder().range(RangeQuery.of(r -> r.untyped(n -> n.
+ field(schema.getKey()).
+ gt(JsonData.of(value))))).
+ build();
+ break;
+
+ case LE:
+ query = new Query.Builder().range(RangeQuery.of(r -> r.untyped(n -> n.
+ field(schema.getKey()).
+ lte(JsonData.of(value))))).
+ build();
+ break;
+
+ case LT:
+ query = new Query.Builder().range(RangeQuery.of(r -> r.untyped(n -> n.
+ field(schema.getKey()).
+ lt(JsonData.of(value))))).
+ build();
+ break;
+
+ default:
+ break;
+ }
+
+ return query;
+ }
+
+ protected Query getQuery(final AttrCond cond) {
+ CheckResult<AttrCond> checked = check(cond);
+ return fillAttrQuery(checked.schema(), checked.value(), cond);
+ }
+
+ protected Query getQuery(final AnyCond cond) {
+ CheckResult<AnyCond> checked = check(
+ cond,
+ realmUtils.getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
+ return fillAttrQuery(checked.schema(), checked.value(), checked.cond());
+ }
+
+ protected Query getQueryForCustomConds(final SearchCond cond) {
+ return null;
}
@Override
- public long countDescendants(final Set<String> bases, final String keyword) {
+ protected long doCount(final Set<String> bases, final SearchCond searchCond) {
CountRequest request = new CountRequest.Builder().
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
- query(buildDescendantsQuery(bases, keyword)).
+ query(buildDescendantsQuery(bases, searchCond)).
build();
LOG.debug("Count request: {}", request);
@@ -190,20 +395,52 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
}
}
- @Override
- public List<Realm> findDescendants(final String base, final String keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected List<SortOptions> sortBuilders(final Stream<Sort.Order> orderBy) {
+ List<SortOptions> options = new ArrayList<>();
+ orderBy.forEach(clause -> {
+ String sortName = null;
+
+ String fieldName = clause.getProperty();
+ // Cannot sort by internal _id
+ if (!ID_PROPS.contains(fieldName)) {
+ Field anyField = realmUtils.getField(fieldName).orElse(null);
+ if (anyField == null) {
+ PlainSchema schema = plainSchemaDAO.findById(fieldName).orElse(null);
+ if (schema != null) {
+ sortName = fieldName;
+ }
+ } else {
+ sortName = fieldName;
+ }
+ }
+
+ if (sortName == null) {
+ LOG.warn("Cannot build any valid clause from {}", clause);
+ } else {
+ if ("fullPath".equals(sortName)) {
+ options.addAll(FULLPATH_SORT_OPTIONS);
+ } else {
+ options.add(new SortOptions.Builder().field(
+ new FieldSort.Builder().
+ field(sortName).
+ order(clause.getDirection() == Sort.Direction.ASC ? SortOrder.Asc : SortOrder.Desc).
+ build()).
+ build());
+ }
+ }
+ });
+ return options.isEmpty() ? FULLPATH_SORT_OPTIONS : options;
}
@Override
- public List<Realm> findDescendants(final Set<String> bases, final String keyword, final Pageable pageable) {
+ protected List<Realm> doSearch(final Set<String> bases, final SearchCond searchCond, final Pageable pageable) {
SearchRequest request = new SearchRequest.Builder().
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
searchType(SearchType.QueryThenFetch).
- query(buildDescendantsQuery(bases, keyword)).
+ query(buildDescendantsQuery(bases, searchCond)).
from(pageable.isUnpaged() ? 0 : pageable.getPageSize() * pageable.getPageNumber()).
size(pageable.isUnpaged() ? indexMaxResultWindow : pageable.getPageSize()).
- sort(REALM_SORT_OPTIONS).
+ sort(sortBuilders(pageable.getSort().get())).
fields(List.of()).source(new SourceConfig.Builder().fetch(false).build()).
build();
LOG.debug("Search request: {}", request);
@@ -222,17 +459,22 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
}
@Override
- public List<String> findDescendants(final String base, final String prefix) {
- Query prefixQuery = new Query.Builder().disMax(QueryBuilders.disMax().queries(
- new Query.Builder().term(QueryBuilders.term().
- field("fullPath").value(prefix).caseInsensitive(false).build()).build(),
- new Query.Builder().prefix(QueryBuilders.prefix().
- field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(prefix) ? "/" : prefix + "/").
- build()).build()).build()).build();
-
- Query query = new Query.Builder().bool(QueryBuilders.bool().filter(
- buildDescendantsQuery(Set.of(base), (String) null), prefixQuery).build()).
- build();
+ public List<Realm> findDescendants(final String base, final String prefix) {
+ Query descendantsQuery = buildDescendantsQuery(Set.of(base), null);
+ Query query;
+ if (prefix == null) {
+ query = descendantsQuery;
+ } else {
+ Query prefixQuery = new Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new Query.Builder().term(QueryBuilders.term().
+ field("fullPath").value(prefix).caseInsensitive(false).build()).build(),
+ new Query.Builder().prefix(QueryBuilders.prefix().
+ field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(prefix) ? "/" : prefix + "/").
+ build()).build()).build()).build();
+ query = new Query.Builder().bool(QueryBuilders.bool().filter(
+ descendantsQuery, prefixQuery).build()).
+ build();
+ }
SearchRequest request = new SearchRequest.Builder().
index(ElasticsearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
@@ -240,7 +482,7 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
query(query).
from(0).
size(indexMaxResultWindow).
- sort(REALM_SORT_OPTIONS).
+ sort(FULLPATH_SORT_OPTIONS).
fields(List.of()).source(new SourceConfig.Builder().fetch(false).build()).
build();
LOG.debug("Search request: {}", request);
@@ -253,6 +495,6 @@ public class ElasticsearchRealmSearchDAO implements RealmSearchDAO {
} catch (Exception e) {
LOG.error("While searching in Elasticsearch with request {}", request, e);
}
- return result;
+ return result.stream().map(realmDAO::findById).flatMap(Optional::stream).map(Realm.class::cast).toList();
}
}
diff --git a/ext/elasticsearch/persistence/src/test/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAOTest.java b/ext/elasticsearch/persistence/src/test/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAOTest.java
index 729346452c..8507d071f7 100644
--- a/ext/elasticsearch/persistence/src/test/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAOTest.java
+++ b/ext/elasticsearch/persistence/src/test/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchAnySearchDAOTest.java
@@ -110,11 +110,12 @@ public class ElasticsearchAnySearchDAOTest {
public void getAdminRealmsFilter4realm() throws IOException {
// 1. mock
Realm root = mock(Realm.class);
+ when(root.getKey()).thenReturn("rootKey");
when(root.getFullPath()).thenReturn(SyncopeConstants.ROOT_REALM);
when(realmSearchDAO.findByFullPath(SyncopeConstants.ROOT_REALM)).thenAnswer(ic -> Optional.of(root));
when(realmSearchDAO.findDescendants(eq(SyncopeConstants.ROOT_REALM), anyString())).
- thenReturn(List.of("rootKey"));
+ thenReturn(List.of(root));
// 2. test
Set<String> adminRealms = Set.of(SyncopeConstants.ROOT_REALM);
diff --git a/ext/elasticsearch/persistence/src/test/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchRealmSearchDAOTest.java b/ext/elasticsearch/persistence/src/test/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchRealmSearchDAOTest.java
new file mode 100644
index 0000000000..ae38c45297
--- /dev/null
+++ b/ext/elasticsearch/persistence/src/test/java/org/apache/syncope/core/persistence/elasticsearch/dao/ElasticsearchRealmSearchDAOTest.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.elasticsearch.dao;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import co.elastic.clients.elasticsearch.ElasticsearchClient;
+import co.elastic.clients.elasticsearch._types.FieldValue;
+import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.DisMaxQuery;
+import co.elastic.clients.elasticsearch._types.query_dsl.Query;
+import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
+import java.util.Optional;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+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.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.jpa.entity.JPAPlainSchema;
+import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class ElasticsearchRealmSearchDAOTest {
+
+ @Mock
+ private RealmDAO realmDAO;
+
+ @Mock
+ private PlainSchemaDAO plainSchemaDAO;
+
+ @Mock
+ private UserDAO userDAO;
+
+ @Mock
+ private GroupDAO groupDAO;
+
+ @Mock
+ private EntityFactory entityFactory;
+
+ @Mock
+ private PlainAttrValidationManager validator;
+
+ @Mock
+ private ElasticsearchClient client;
+
+ private ElasticsearchRealmSearchDAO searchDAO;
+
+ @BeforeEach
+ protected void setupSearchDAO() {
+ doReturn(JPARealm.class).when(entityFactory).realmClass();
+ searchDAO = new ElasticsearchRealmSearchDAO(
+ realmDAO,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ client,
+ 10000);
+ }
+
+ @Test
+ public void query4anyCond() {
+ when(entityFactory.newEntity(PlainSchema.class)).thenReturn(new JPAPlainSchema());
+ doAnswer(ic -> {
+ PlainAttrValue value = ic.getArgument(2);
+ value.setStringValue(ic.getArgument(1));
+ return null;
+ }).when(validator).validate(any(PlainSchema.class), anyString(), any(PlainAttrValue.class));
+
+ AnyCond name = new AnyCond(AttrCond.Type.EQ);
+ name.setSchema("name");
+ name.setExpression("two");
+
+ Query query = searchDAO.getQuery(SearchCond.of(name));
+ assertThat(
+ new Query.Builder().term(QueryBuilders.term().
+ field("name").value(FieldValue.of("two")).caseInsensitive(false).build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+ verifyNoInteractions(plainSchemaDAO);
+ }
+
+ @Test
+ public void query4attrCond() {
+ PlainSchema aLong = new JPAPlainSchema();
+ aLong.setKey("aLong");
+ aLong.setType(AttrSchemaType.Long);
+ doReturn(Optional.of(aLong)).when(plainSchemaDAO).findById("aLong");
+ doAnswer(ic -> {
+ PlainAttrValue value = ic.getArgument(2);
+ value.setLongValue(Long.valueOf(ic.getArgument(1)));
+ return null;
+ }).when(validator).validate(any(PlainSchema.class), anyString(), any(PlainAttrValue.class));
+
+ AttrCond attrEq = new AttrCond(AttrCond.Type.EQ);
+ attrEq.setSchema("aLong");
+ attrEq.setExpression("42");
+
+ Query query = searchDAO.getQuery(SearchCond.of(attrEq));
+ assertEquals(Query.Kind.Term, query._kind());
+ assertEquals("aLong", query.term().field());
+ assertEquals(Boolean.FALSE, query.term().caseInsensitive());
+ assertEquals("42", query.term().value().anyValue().toString());
+ }
+
+ @Test
+ public void query4attrCondNullChecks() {
+ PlainSchema aLong = new JPAPlainSchema();
+ aLong.setKey("aLong");
+ aLong.setType(AttrSchemaType.Long);
+ doReturn(Optional.of(aLong)).when(plainSchemaDAO).findById("aLong");
+
+ AttrCond isNull = new AttrCond(AttrCond.Type.ISNULL);
+ isNull.setSchema("aLong");
+
+ Query query = searchDAO.getQuery(SearchCond.of(isNull));
+ assertThat(
+ new Query.Builder().bool(QueryBuilders.bool().mustNot(
+ new Query.Builder().exists(QueryBuilders.exists().field("aLong").build())
+ .build()).build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+
+ AttrCond isNotNull = new AttrCond(AttrCond.Type.ISNOTNULL);
+ isNotNull.setSchema("aLong");
+
+ query = searchDAO.getQuery(SearchCond.of(isNotNull));
+ assertThat(
+ new Query.Builder().exists(QueryBuilders.exists().field("aLong").build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+ }
+
+ @Test
+ public void descendantsQueryWithAndOrFilters() {
+ when(entityFactory.newEntity(PlainSchema.class)).thenReturn(new JPAPlainSchema());
+ doAnswer(ic -> {
+ PlainAttrValue value = ic.getArgument(2);
+ value.setStringValue(ic.getArgument(1));
+ return null;
+ }).when(validator).validate(any(PlainSchema.class), anyString(), any(PlainAttrValue.class));
+
+ AnyCond nameTwo = new AnyCond(AttrCond.Type.EQ);
+ nameTwo.setSchema("name");
+ nameTwo.setExpression("two");
+
+ AnyCond nameOdd = new AnyCond(AttrCond.Type.EQ);
+ nameOdd.setSchema("name");
+ nameOdd.setExpression("odd");
+
+ Query query = searchDAO.buildDescendantsQuery(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.or(SearchCond.of(nameTwo), SearchCond.of(nameOdd)));
+
+ assertEquals(Query.Kind.Bool, query._kind());
+ assertEquals(2, ((BoolQuery) query._get()).filter().size());
+ Query right = ((BoolQuery) query._get()).filter().get(1);
+ assertEquals(Query.Kind.DisMax, right._kind());
+ assertEquals(2, ((DisMaxQuery) right._get()).queries().size());
+
+ assertThat(
+ new Query.Builder().bool(QueryBuilders.bool().
+ filter(new Query.Builder().disMax(QueryBuilders.disMax().
+ queries(new Query.Builder().term(QueryBuilders.term().
+ field("fullPath").value(FieldValue.of(SyncopeConstants.ROOT_REALM)).
+ caseInsensitive(false).build()).build()).
+ queries(new Query.Builder().regexp(QueryBuilders.regexp().
+ field("fullPath").value("/.*").build()).build()).
+ build()).build()).
+ filter(new Query.Builder().disMax(QueryBuilders.disMax().
+ queries(new Query.Builder().term(QueryBuilders.term().
+ field("name").value(FieldValue.of("two")).caseInsensitive(false).
+ build()).build()).
+ queries(new Query.Builder().term(QueryBuilders.term().
+ field("name").value(FieldValue.of("odd")).caseInsensitive(false).
+ build()).build()).
+ build()).build()).
+ build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+ }
+}
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 d951980637..22c864f3a2 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
@@ -90,6 +90,17 @@ public class OpenSearchUtils {
builder.put("relationshipTypes", relationshipTypes);
}
+ protected void addPlainAttr(final Map<String, Object> builder, final List<PlainAttr> plainAttrs) {
+ for (PlainAttr plainAttr : plainAttrs) {
+ List<Object> values = plainAttr.getValues().stream().
+ map(PlainAttrValue::getValue).collect(Collectors.toList());
+
+ Optional.ofNullable(plainAttr.getUniqueValue()).ifPresent(v -> values.add(v.getValue()));
+
+ builder.put(plainAttr.getSchema(), values.size() == 1 ? values.getFirst() : values);
+ }
+ }
+
/**
* Returns the document specialized with content from the provided any.
*
@@ -165,14 +176,7 @@ public class OpenSearchUtils {
}
}
- for (PlainAttr plainAttr : any.getPlainAttrs()) {
- List<Object> values = plainAttr.getValues().stream().
- map(PlainAttrValue::getValue).collect(Collectors.toList());
-
- Optional.ofNullable(plainAttr.getUniqueValue()).ifPresent(v -> values.add(v.getValue()));
-
- builder.put(plainAttr.getSchema(), values.size() == 1 ? values.getFirst() : values);
- }
+ addPlainAttr(builder, any.getPlainAttrs());
// add also flattened membership attributes
if (any instanceof Groupable<?, ?, ?> groupable) {
@@ -210,6 +214,7 @@ public class OpenSearchUtils {
builder.put("name", realm.getName());
builder.put("parent_id", realm.getParent() == null ? null : realm.getParent().getKey());
builder.put("fullPath", realm.getFullPath());
+ addPlainAttr(builder, realm.getPlainAttrs());
customizeDocument(builder, realm);
@@ -235,8 +240,6 @@ public class OpenSearchUtils {
return builder;
}
- protected void customizeDocument(
- final Map<String, Object> builder,
- final AuditEvent auditEvent) {
+ protected void customizeDocument(final Map<String, Object> builder, final AuditEvent auditEvent) {
}
}
diff --git a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/OpenSearchPersistenceContext.java b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/OpenSearchPersistenceContext.java
index c58bb061bf..e184bc636e 100644
--- a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/OpenSearchPersistenceContext.java
+++ b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/OpenSearchPersistenceContext.java
@@ -74,10 +74,23 @@ public class OpenSearchPersistenceContext {
@Bean
public RealmSearchDAO realmSearchDAO(
final @Lazy RealmDAO realmDAO,
+ final @Lazy PlainSchemaDAO plainSchemaDAO,
+ final @Lazy UserDAO userDAO,
+ final @Lazy GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
final OpenSearchClient client,
final OpenSearchProperties props) {
- return new OpenSearchRealmSearchDAO(realmDAO, client, props.getIndexMaxResultWindow());
+ return new OpenSearchRealmSearchDAO(
+ realmDAO,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ client,
+ props.getIndexMaxResultWindow());
}
@ConditionalOnMissingBean(name = "openSearchAuditEventDAO")
diff --git a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java
index d4e973f36c..d27fb819f1 100644
--- a/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java
+++ b/ext/opensearch/persistence/src/main/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAO.java
@@ -142,7 +142,7 @@ public class OpenSearchAnySearchDAO extends AbstractAnySearchDAO {
});
realmSearchDAO.findDescendants(realm.getFullPath(), base.getFullPath()).
- forEach(descendant -> queries.add(
+ stream().map(Realm::getKey).forEach(descendant -> queries.add(
new Query.Builder().term(QueryBuilders.term().
field("realm").value(FieldValue.of(descendant)).caseInsensitive(false).build()).
build()));
@@ -565,8 +565,8 @@ public class OpenSearchAnySearchDAO extends AbstractAnySearchDAO {
}
@Override
- protected CheckResult<AnyCond> check(final AnyCond cond, final AnyTypeKind kind) {
- CheckResult<AnyCond> checked = super.check(cond, kind);
+ protected CheckResult<AnyCond> check(final AnyCond cond, final Field field, final Set<String> relationshipsFields) {
+ CheckResult<AnyCond> checked = super.check(cond, field, relationshipsFields);
// Manage difference between external id attribute and internal _id
if ("id".equals(checked.cond().getSchema())) {
@@ -586,7 +586,11 @@ public class OpenSearchAnySearchDAO extends AbstractAnySearchDAO {
cond.setExpression(realm.getKey());
}
- CheckResult<AnyCond> checked = check(cond, kind);
+ CheckResult<AnyCond> checked = check(
+ cond,
+ anyUtilsFactory.getInstance(kind).getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
return fillAttrQuery(checked.schema(), checked.value(), checked.cond());
}
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 1f3618d6be..6b9415d383 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
@@ -18,26 +18,45 @@
*/
package org.apache.syncope.core.persistence.opensearch.dao;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
import org.apache.syncope.core.persistence.api.dao.MalformedPathException;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
import org.apache.syncope.core.persistence.api.dao.RealmDAO;
import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
+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.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+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.Realm;
+import org.apache.syncope.core.persistence.api.utils.FormatUtils;
+import org.apache.syncope.core.persistence.api.utils.RealmUtils;
+import org.apache.syncope.core.persistence.common.dao.AbstractRealmSearchDAO;
import org.apache.syncope.core.spring.security.AuthContextUtils;
import org.apache.syncope.ext.opensearch.client.OpenSearchUtils;
+import org.opensearch.client.json.JsonData;
import org.opensearch.client.opensearch.OpenSearchClient;
import org.opensearch.client.opensearch._types.BuiltinScriptLanguage;
+import org.opensearch.client.opensearch._types.FieldSort;
import org.opensearch.client.opensearch._types.FieldValue;
import org.opensearch.client.opensearch._types.ScriptLanguage;
import org.opensearch.client.opensearch._types.ScriptSortType;
import org.opensearch.client.opensearch._types.SearchType;
import org.opensearch.client.opensearch._types.SortOptions;
import org.opensearch.client.opensearch._types.SortOrder;
+import org.opensearch.client.opensearch._types.query_dsl.BoolQuery;
import org.opensearch.client.opensearch._types.query_dsl.Query;
import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders;
import org.opensearch.client.opensearch.core.CountRequest;
@@ -47,13 +66,16 @@ import org.opensearch.client.opensearch.core.search.SourceConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
import org.springframework.transaction.annotation.Transactional;
-public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
+public class OpenSearchRealmSearchDAO extends AbstractRealmSearchDAO implements RealmSearchDAO {
- protected static final Logger LOG = LoggerFactory.getLogger(RealmDAO.class);
+ protected static final Logger LOG = LoggerFactory.getLogger(RealmSearchDAO.class);
- protected static final List<SortOptions> REALM_SORT_OPTIONS = List.of(
+ protected static final Set<String> ID_PROPS = Set.of("key", "id", "_id");
+
+ protected static final List<SortOptions> FULLPATH_SORT_OPTIONS = List.of(
new SortOptions.Builder().
script(s -> s.type(ScriptSortType.Number).
script(t -> t.inline(i -> i.lang(ScriptLanguage.builder().
@@ -64,16 +86,25 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
protected final RealmDAO realmDAO;
+ protected final RealmUtils realmUtils;
+
protected final OpenSearchClient client;
protected final int indexMaxResultWindow;
public OpenSearchRealmSearchDAO(
final RealmDAO realmDAO,
+ final PlainSchemaDAO plainSchemaDAO,
+ final UserDAO userDAO,
+ final GroupDAO groupDAO,
+ final EntityFactory entityFactory,
+ final PlainAttrValidationManager validator,
final OpenSearchClient client,
final int indexMaxResultWindow) {
+ super(plainSchemaDAO, userDAO, groupDAO, entityFactory, validator);
this.realmDAO = realmDAO;
+ this.realmUtils = new RealmUtils(entityFactory);
this.client = client;
this.indexMaxResultWindow = indexMaxResultWindow;
}
@@ -103,7 +134,7 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
return client.search(request, Void.class).hits().hits().stream().findFirst().map(Hit::id).
flatMap(realmDAO::findById).map(Realm.class::cast);
} catch (Exception e) {
- LOG.error("While searching OpenSearch for Realm path {} with request {}", fullPath, request, e);
+ LOG.error("While searching Elasticsearch for Realm path {} with request {}", fullPath, request, e);
}
return Optional.empty();
@@ -114,7 +145,7 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
searchType(SearchType.QueryThenFetch).
query(query).
- sort(REALM_SORT_OPTIONS).
+ sort(FULLPATH_SORT_OPTIONS).
fields(List.of()).source(new SourceConfig.Builder().fetch(false).build()).
build();
LOG.debug("Search request: {}", request);
@@ -124,7 +155,7 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
map(Hit::id).
toList();
} catch (Exception e) {
- LOG.error("While searching in OpenSearch with request {}", request, e);
+ LOG.error("While searching in Elasticsearch with request {}", request, e);
return List.of();
}
}
@@ -148,7 +179,7 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
flatMap(Optional::stream).map(Realm.class::cast).toList();
}
- protected Query buildDescendantsQuery(final Set<String> bases, final String keyword) {
+ protected Query buildDescendantsQuery(final Set<String> bases, final SearchCond searchCond) {
List<Query> basesQueries = new ArrayList<>();
bases.forEach(base -> {
basesQueries.add(new Query.Builder().term(QueryBuilders.term().
@@ -159,53 +190,252 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
});
Query prefix = new Query.Builder().disMax(QueryBuilders.disMax().queries(basesQueries).build()).build();
- if (keyword == null) {
- return prefix;
+ BoolQuery.Builder boolBuilder = QueryBuilders.bool().filter(prefix);
+ if (searchCond != null) {
+ boolBuilder.filter(getQuery(searchCond));
}
+ return new Query.Builder().bool(boolBuilder.build()).build();
+ }
- return new Query.Builder().bool(QueryBuilders.bool().filter(
- prefix,
- new Query.Builder().wildcard(QueryBuilders.wildcard().
- field("name").value(keyword.replace('%', '*').replace("\\_", "_")).
- caseInsensitive(true).build()).build()).build()).
- build();
+ protected Query getQuery(final SearchCond cond) {
+ Query query = null;
+
+ switch (cond.getType()) {
+ case LEAF, NOT_LEAF -> {
+ query = cond.asLeaf(AnyCond.class).map(this::getQuery).orElse(null);
+ if (query == null) {
+ query = cond.asLeaf(AttrCond.class).map(this::getQuery).orElse(null);
+ }
+ if (query == null) {
+ query = getQueryForCustomConds(cond);
+ }
+ if (query == null) {
+ throw new IllegalArgumentException("Cannot construct QueryBuilder");
+ }
+ if (cond.getType() == SearchCond.Type.NOT_LEAF) {
+ query = new Query.Builder().bool(QueryBuilders.bool().mustNot(query).build()).build();
+ }
+ }
+ case AND -> {
+ List<Query> andCompound = new ArrayList<>();
+ Query andLeft = getQuery(cond.getLeft());
+ if (andLeft.isBool() && !andLeft.bool().filter().isEmpty()) {
+ andCompound.addAll(andLeft.bool().filter());
+ } else {
+ andCompound.add(andLeft);
+ }
+ Query andRight = getQuery(cond.getRight());
+ if (andRight.isBool() && !andRight.bool().filter().isEmpty()) {
+ andCompound.addAll(andRight.bool().filter());
+ } else {
+ andCompound.add(andRight);
+ }
+ query = new Query.Builder().bool(QueryBuilders.bool().filter(andCompound).build()).build();
+ }
+ case OR -> {
+ List<Query> orCompound = new ArrayList<>();
+ Query orLeft = getQuery(cond.getLeft());
+ if (orLeft.isDisMax()) {
+ orCompound.addAll(orLeft.disMax().queries());
+ } else {
+ orCompound.add(orLeft);
+ }
+ Query orRight = getQuery(cond.getRight());
+ if (orRight.isDisMax()) {
+ orCompound.addAll(orRight.disMax().queries());
+ } else {
+ orCompound.add(orRight);
+ }
+ query = new Query.Builder().disMax(QueryBuilders.disMax().queries(orCompound).build()).build();
+ }
+ default -> {
+ }
+ }
+
+ return query;
}
@Override
- public long countDescendants(final String base, final String keyword) {
- return countDescendants(Set.of(base), keyword);
+ protected CheckResult<AnyCond> check(final AnyCond cond, final Field field, final Set<String> relationshipsFields) {
+ CheckResult<AnyCond> checked = super.check(cond, field, relationshipsFields);
+
+ // Manage difference between external id attribute and internal _id
+ if ("id".equals(checked.cond().getSchema())) {
+ checked.cond().setSchema("_id");
+ }
+ if ("id".equals(checked.schema().getKey())) {
+ checked.schema().setKey("_id");
+ }
+
+ return checked;
+ }
+
+ protected Query fillAttrQuery(
+ final PlainSchema schema,
+ final PlainAttrValue attrValue,
+ final AttrCond cond) {
+
+ Object value = schema.getType() == AttrSchemaType.Date && attrValue.getDateValue() != null
+ ? FormatUtils.format(attrValue.getDateValue())
+ : attrValue.getValue();
+
+ Query query = null;
+ switch (cond.getType()) {
+ case ISNOTNULL:
+ query = new Query.Builder().exists(QueryBuilders.exists().field(schema.getKey()).build()).build();
+ break;
+
+ case ISNULL:
+ query = new Query.Builder().bool(QueryBuilders.bool().mustNot(
+ new Query.Builder().exists(QueryBuilders.exists().field(schema.getKey()).build())
+ .build()).build()).build();
+ break;
+
+ case ILIKE:
+ query = new Query.Builder().wildcard(QueryBuilders.wildcard().
+ field(schema.getKey()).value(cond.getExpression().replace('%', '*').replace("\\_", "_")).
+ caseInsensitive(true).build()).build();
+ break;
+
+ case LIKE:
+ query = new Query.Builder().wildcard(QueryBuilders.wildcard().
+ field(schema.getKey()).value(cond.getExpression().replace('%', '*').replace("\\_", "_")).
+ caseInsensitive(false).build()).build();
+ break;
+
+ case IEQ:
+ query = new Query.Builder().term(QueryBuilders.term().
+ field(schema.getKey()).value(FieldValue.of(cond.getExpression())).caseInsensitive(true).
+ build()).build();
+ break;
+
+ case EQ:
+ FieldValue fieldValue = switch (value) {
+ case Double aDouble ->
+ FieldValue.of(aDouble);
+ case Long aLong ->
+ FieldValue.of(aLong);
+ case Boolean aBoolean ->
+ FieldValue.of(aBoolean);
+ default ->
+ FieldValue.of(value.toString());
+ };
+ query = new Query.Builder().term(QueryBuilders.term().
+ field(schema.getKey()).value(fieldValue).caseInsensitive(false).build()).
+ build();
+ break;
+
+ case GE:
+ query = new Query.Builder().range(QueryBuilders.range().
+ field(schema.getKey()).gte(JsonData.of(value)).build()).
+ build();
+ break;
+
+ case GT:
+ query = new Query.Builder().range(QueryBuilders.range().
+ field(schema.getKey()).gt(JsonData.of(value)).build()).
+ build();
+ break;
+
+ case LE:
+ query = new Query.Builder().range(QueryBuilders.range().
+ field(schema.getKey()).lte(JsonData.of(value)).build()).
+ build();
+ break;
+
+ case LT:
+ query = new Query.Builder().range(QueryBuilders.range().
+ field(schema.getKey()).lt(JsonData.of(value)).build()).
+ build();
+ break;
+
+ default:
+ break;
+ }
+
+ return query;
+ }
+
+ protected Query getQuery(final AttrCond cond) {
+ CheckResult<AttrCond> checked = check(cond);
+ return fillAttrQuery(checked.schema(), checked.value(), cond);
+ }
+
+ protected Query getQuery(final AnyCond cond) {
+ CheckResult<AnyCond> checked = check(
+ cond,
+ realmUtils.getField(cond.getSchema()).
+ orElseThrow(() -> new IllegalArgumentException("Invalid schema " + cond.getSchema())),
+ RELATIONSHIP_FIELDS);
+ return fillAttrQuery(checked.schema(), checked.value(), checked.cond());
+ }
+
+ protected Query getQueryForCustomConds(final SearchCond cond) {
+ return null;
}
@Override
- public long countDescendants(final Set<String> bases, final String keyword) {
+ protected long doCount(final Set<String> bases, final SearchCond searchCond) {
CountRequest request = new CountRequest.Builder().
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
- query(buildDescendantsQuery(bases, keyword)).
+ query(buildDescendantsQuery(bases, searchCond)).
build();
LOG.debug("Count request: {}", request);
try {
return client.count(request).count();
} catch (Exception e) {
- LOG.error("While counting in OpenSearch with request {}", request, e);
+ LOG.error("While counting in Elasticsearch with request {}", request, e);
return 0;
}
}
- @Override
- public List<Realm> findDescendants(final String base, final String keyword, final Pageable pageable) {
- return findDescendants(Set.of(base), keyword, pageable);
+ protected List<SortOptions> sortBuilders(final Stream<Sort.Order> orderBy) {
+ List<SortOptions> options = new ArrayList<>();
+ orderBy.forEach(clause -> {
+ String sortName = null;
+
+ String fieldName = clause.getProperty();
+ // Cannot sort by internal _id
+ if (!ID_PROPS.contains(fieldName)) {
+ Field anyField = realmUtils.getField(fieldName).orElse(null);
+ if (anyField == null) {
+ PlainSchema schema = plainSchemaDAO.findById(fieldName).orElse(null);
+ if (schema != null) {
+ sortName = fieldName;
+ }
+ } else {
+ sortName = fieldName;
+ }
+ }
+
+ if (sortName == null) {
+ LOG.warn("Cannot build any valid clause from {}", clause);
+ } else {
+ if ("fullPath".equals(sortName)) {
+ options.addAll(FULLPATH_SORT_OPTIONS);
+ } else {
+ options.add(new SortOptions.Builder().field(
+ new FieldSort.Builder().
+ field(sortName).
+ order(clause.getDirection() == Sort.Direction.ASC ? SortOrder.Asc : SortOrder.Desc).
+ build()).
+ build());
+ }
+ }
+ });
+ return options.isEmpty() ? FULLPATH_SORT_OPTIONS : options;
}
@Override
- public List<Realm> findDescendants(final Set<String> bases, final String keyword, final Pageable pageable) {
+ protected List<Realm> doSearch(final Set<String> bases, final SearchCond searchCond, final Pageable pageable) {
SearchRequest request = new SearchRequest.Builder().
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
searchType(SearchType.QueryThenFetch).
- query(buildDescendantsQuery(bases, keyword)).
+ query(buildDescendantsQuery(bases, searchCond)).
from(pageable.isUnpaged() ? 0 : pageable.getPageSize() * pageable.getPageNumber()).
size(pageable.isUnpaged() ? indexMaxResultWindow : pageable.getPageSize()).
- sort(REALM_SORT_OPTIONS).
+ sort(sortBuilders(pageable.getSort().get())).
fields(List.of()).source(new SourceConfig.Builder().fetch(false).build()).
build();
LOG.debug("Search request: {}", request);
@@ -216,7 +446,7 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
map(Hit::id).
toList();
} catch (Exception e) {
- LOG.error("While searching in OpenSearch with request {}", request, e);
+ LOG.error("While searching in Elasticsearch with request {}", request, e);
}
return result.stream().map(realmDAO::findById).
@@ -224,17 +454,22 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
}
@Override
- public List<String> findDescendants(final String base, final String prefix) {
- Query prefixQuery = new Query.Builder().disMax(QueryBuilders.disMax().queries(
- new Query.Builder().term(QueryBuilders.term().
- field("fullPath").value(FieldValue.of(prefix)).caseInsensitive(false).build()).build(),
- new Query.Builder().prefix(QueryBuilders.prefix().
- field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(prefix) ? "/" : prefix + "/").
- build()).build()).build()).build();
-
- Query query = new Query.Builder().bool(QueryBuilders.bool().filter(
- buildDescendantsQuery(Set.of(base), (String) null), prefixQuery).build()).
- build();
+ public List<Realm> findDescendants(final String base, final String prefix) {
+ Query descendantsQuery = buildDescendantsQuery(Set.of(base), null);
+ Query query;
+ if (prefix == null) {
+ query = descendantsQuery;
+ } else {
+ Query prefixQuery = new Query.Builder().disMax(QueryBuilders.disMax().queries(
+ new Query.Builder().term(QueryBuilders.term().
+ field("fullPath").value(FieldValue.of(prefix)).caseInsensitive(false).build()).build(),
+ new Query.Builder().prefix(QueryBuilders.prefix().
+ field("fullPath").value(SyncopeConstants.ROOT_REALM.equals(prefix) ? "/" : prefix + "/").
+ build()).build()).build()).build();
+ query = new Query.Builder().bool(QueryBuilders.bool().filter(
+ descendantsQuery, prefixQuery).build()).
+ build();
+ }
SearchRequest request = new SearchRequest.Builder().
index(OpenSearchUtils.getRealmIndex(AuthContextUtils.getDomain())).
@@ -242,7 +477,7 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
query(query).
from(0).
size(indexMaxResultWindow).
- sort(REALM_SORT_OPTIONS).
+ sort(FULLPATH_SORT_OPTIONS).
fields(List.of()).source(new SourceConfig.Builder().fetch(false).build()).
build();
LOG.debug("Search request: {}", request);
@@ -253,8 +488,8 @@ public class OpenSearchRealmSearchDAO implements RealmSearchDAO {
map(Hit::id).
toList();
} catch (Exception e) {
- LOG.error("While searching in OpenSearch", e);
+ LOG.error("While searching in Elasticsearch with request {}", request, e);
}
- return result;
+ return result.stream().map(realmDAO::findById).flatMap(Optional::stream).map(Realm.class::cast).toList();
}
}
diff --git a/ext/opensearch/persistence/src/test/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAOTest.java b/ext/opensearch/persistence/src/test/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAOTest.java
index 86f99f3ec9..20998de3ba 100644
--- a/ext/opensearch/persistence/src/test/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAOTest.java
+++ b/ext/opensearch/persistence/src/test/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchAnySearchDAOTest.java
@@ -111,11 +111,12 @@ public class OpenSearchAnySearchDAOTest {
public void getAdminRealmsFilter4realm() throws IOException {
// 1. mock
Realm root = mock(Realm.class);
+ when(root.getKey()).thenReturn("rootKey");
when(root.getFullPath()).thenReturn(SyncopeConstants.ROOT_REALM);
when(realmSearchDAO.findByFullPath(SyncopeConstants.ROOT_REALM)).thenAnswer(ic -> Optional.of(root));
when(realmSearchDAO.findDescendants(eq(SyncopeConstants.ROOT_REALM), anyString())).
- thenReturn(List.of("rootKey"));
+ thenReturn(List.of(root));
// 2. test
Set<String> adminRealms = Set.of(SyncopeConstants.ROOT_REALM);
diff --git a/ext/opensearch/persistence/src/test/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchRealmSearchDAOTest.java b/ext/opensearch/persistence/src/test/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchRealmSearchDAOTest.java
new file mode 100644
index 0000000000..90ff254376
--- /dev/null
+++ b/ext/opensearch/persistence/src/test/java/org/apache/syncope/core/persistence/opensearch/dao/OpenSearchRealmSearchDAOTest.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.opensearch.dao;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+import java.util.Optional;
+import java.util.Set;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.core.persistence.api.attrvalue.PlainAttrValidationManager;
+import org.apache.syncope.core.persistence.api.dao.GroupDAO;
+import org.apache.syncope.core.persistence.api.dao.PlainSchemaDAO;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+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.AttrCond;
+import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.syncope.core.persistence.jpa.entity.JPAPlainSchema;
+import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opensearch.client.opensearch.OpenSearchClient;
+import org.opensearch.client.opensearch._types.FieldValue;
+import org.opensearch.client.opensearch._types.query_dsl.BoolQuery;
+import org.opensearch.client.opensearch._types.query_dsl.DisMaxQuery;
+import org.opensearch.client.opensearch._types.query_dsl.Query;
+import org.opensearch.client.opensearch._types.query_dsl.QueryBuilders;
+
+@ExtendWith(MockitoExtension.class)
+public class OpenSearchRealmSearchDAOTest {
+
+ @Mock
+ private RealmDAO realmDAO;
+
+ @Mock
+ private PlainSchemaDAO plainSchemaDAO;
+
+ @Mock
+ private UserDAO userDAO;
+
+ @Mock
+ private GroupDAO groupDAO;
+
+ @Mock
+ private EntityFactory entityFactory;
+
+ @Mock
+ private PlainAttrValidationManager validator;
+
+ @Mock
+ private OpenSearchClient client;
+
+ private OpenSearchRealmSearchDAO searchDAO;
+
+ @BeforeEach
+ protected void setupSearchDAO() {
+ doReturn(JPARealm.class).when(entityFactory).realmClass();
+ searchDAO = new OpenSearchRealmSearchDAO(
+ realmDAO,
+ plainSchemaDAO,
+ userDAO,
+ groupDAO,
+ entityFactory,
+ validator,
+ client,
+ 10000);
+ }
+
+ @Test
+ public void query4anyCond() {
+ when(entityFactory.newEntity(PlainSchema.class)).thenReturn(new JPAPlainSchema());
+ doAnswer(ic -> {
+ PlainAttrValue value = ic.getArgument(2);
+ value.setStringValue(ic.getArgument(1));
+ return null;
+ }).when(validator).validate(any(PlainSchema.class), anyString(), any(PlainAttrValue.class));
+
+ AnyCond name = new AnyCond(AttrCond.Type.EQ);
+ name.setSchema("name");
+ name.setExpression("two");
+
+ Query query = searchDAO.getQuery(SearchCond.of(name));
+ assertThat(
+ new Query.Builder().term(QueryBuilders.term().
+ field("name").value(FieldValue.of("two")).caseInsensitive(false).build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+ verifyNoInteractions(plainSchemaDAO);
+ }
+
+ @Test
+ public void query4attrCond() {
+ PlainSchema aLong = new JPAPlainSchema();
+ aLong.setKey("aLong");
+ aLong.setType(AttrSchemaType.Long);
+ doReturn(Optional.of(aLong)).when(plainSchemaDAO).findById("aLong");
+ doAnswer(ic -> {
+ PlainAttrValue value = ic.getArgument(2);
+ value.setLongValue(Long.valueOf(ic.getArgument(1)));
+ return null;
+ }).when(validator).validate(any(PlainSchema.class), anyString(), any(PlainAttrValue.class));
+
+ AttrCond attrEq = new AttrCond(AttrCond.Type.EQ);
+ attrEq.setSchema("aLong");
+ attrEq.setExpression("42");
+
+ Query query = searchDAO.getQuery(SearchCond.of(attrEq));
+ assertEquals(Query.Kind.Term, query._kind());
+ assertEquals("aLong", query.term().field());
+ assertEquals(Boolean.FALSE, query.term().caseInsensitive());
+ assertEquals(42, query.term().value().longValue());
+ }
+
+ @Test
+ public void query4attrCondNullChecks() {
+ PlainSchema aLong = new JPAPlainSchema();
+ aLong.setKey("aLong");
+ aLong.setType(AttrSchemaType.Long);
+ doReturn(Optional.of(aLong)).when(plainSchemaDAO).findById("aLong");
+
+ AttrCond isNull = new AttrCond(AttrCond.Type.ISNULL);
+ isNull.setSchema("aLong");
+
+ Query query = searchDAO.getQuery(SearchCond.of(isNull));
+ assertThat(
+ new Query.Builder().bool(QueryBuilders.bool().mustNot(
+ new Query.Builder().exists(QueryBuilders.exists().field("aLong").build())
+ .build()).build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+
+ AttrCond isNotNull = new AttrCond(AttrCond.Type.ISNOTNULL);
+ isNotNull.setSchema("aLong");
+
+ query = searchDAO.getQuery(SearchCond.of(isNotNull));
+ assertThat(
+ new Query.Builder().exists(QueryBuilders.exists().field("aLong").build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+ }
+
+ @Test
+ public void descendantsQueryWithAndOrFilters() {
+ when(entityFactory.newEntity(PlainSchema.class)).thenReturn(new JPAPlainSchema());
+ doAnswer(ic -> {
+ PlainAttrValue value = ic.getArgument(2);
+ value.setStringValue(ic.getArgument(1));
+ return null;
+ }).when(validator).validate(any(PlainSchema.class), anyString(), any(PlainAttrValue.class));
+
+ AnyCond nameTwo = new AnyCond(AttrCond.Type.EQ);
+ nameTwo.setSchema("name");
+ nameTwo.setExpression("two");
+
+ AnyCond nameOdd = new AnyCond(AttrCond.Type.EQ);
+ nameOdd.setSchema("name");
+ nameOdd.setExpression("odd");
+
+ Query query = searchDAO.buildDescendantsQuery(
+ Set.of(SyncopeConstants.ROOT_REALM),
+ SearchCond.or(SearchCond.of(nameTwo), SearchCond.of(nameOdd)));
+
+ assertEquals(Query.Kind.Bool, query._kind());
+ assertEquals(2, ((BoolQuery) query._get()).filter().size());
+ Query right = ((BoolQuery) query._get()).filter().get(1);
+ assertEquals(Query.Kind.DisMax, right._kind());
+ assertEquals(2, ((DisMaxQuery) right._get()).queries().size());
+
+ assertThat(
+ new Query.Builder().bool(QueryBuilders.bool().
+ filter(new Query.Builder().disMax(QueryBuilders.disMax().
+ queries(new Query.Builder().term(QueryBuilders.term().
+ field("fullPath").value(FieldValue.of(SyncopeConstants.ROOT_REALM)).
+ caseInsensitive(false).build()).build()).
+ queries(new Query.Builder().regexp(QueryBuilders.regexp().
+ field("fullPath").value("/.*").build()).build()).
+ build()).build()).
+ filter(new Query.Builder().disMax(QueryBuilders.disMax().
+ queries(new Query.Builder().term(QueryBuilders.term().
+ field("name").value(FieldValue.of("two")).caseInsensitive(false).
+ build()).build()).
+ queries(new Query.Builder().term(QueryBuilders.term().
+ field("name").value(FieldValue.of("odd")).caseInsensitive(false).
+ build()).build()).
+ build()).build()).
+ build()).build()).
+ usingRecursiveComparison().isEqualTo(query);
+ }
+}
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java
index a43bb8f095..8361c24869 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/TestCommand.java
@@ -46,7 +46,7 @@ public class TestCommand implements Command<TestCommandArgs> {
private AnyObjectLogic anyObjectLogic;
private Optional<RealmTO> getRealm(final String fullPath) {
- return realmLogic.search(null, Set.of(fullPath), Pageable.unpaged()).get().
+ return realmLogic.search(Set.of(fullPath), null, Pageable.unpaged()).get().
filter(realm -> fullPath.equals(realm.getFullPath())).findFirst();
}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 69c56addaf..039caf429b 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -518,11 +518,11 @@ public abstract class AbstractITCase {
IS_FLOWABLE_ENABLED = uwfAdapter.get("resource").asString().contains("Flowable");
JsonNode anySearchDAO = beans.findValues("anySearchDAO").getFirst();
- boolean isElasticsearchEnabled = anySearchDAO.get("type").asString().contains("Elasticsearch");
- boolean isOpenSearchEnabled = anySearchDAO.get("type").asString().contains("OpenSearch");
+ boolean isElasticsearchEnabled = anySearchDAO.get("resource").asString().contains("Elasticsearch");
+ boolean isOpenSearchEnabled = anySearchDAO.get("resource").asString().contains("OpenSearch");
IS_EXT_SEARCH_ENABLED = isElasticsearchEnabled || isOpenSearchEnabled;
- IS_NEO4J_PERSISTENCE = anySearchDAO.get("type").asString().contains("Neo4j");
+ IS_NEO4J_PERSISTENCE = anySearchDAO.get("resource").asString().contains("neo4j");
if (!IS_EXT_SEARCH_ENABLED) {
return;
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java
index 0e062f5e1c..a9f729ab1b 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/MultitenancyITCase.java
@@ -97,7 +97,7 @@ public class MultitenancyITCase extends AbstractITCase {
@Test
public void readRealm() {
PagedResult<RealmTO> realms = ADMIN_CLIENT.getService(RealmService.class).
- search(new RealmQuery.Builder().keyword("*").build());
+ search(new RealmQuery.Builder().build());
assertEquals(1, realms.getTotalCount());
assertEquals(1, realms.getResult().size());
assertEquals(SyncopeConstants.ROOT_REALM, realms.getResult().getFirst().getName());
@@ -106,7 +106,7 @@ public class MultitenancyITCase extends AbstractITCase {
@Test
public void createUser() {
assertNull(ADMIN_CLIENT.getService(RealmService.class).
- search(new RealmQuery.Builder().keyword("*").build()).getResult().getFirst().getPasswordPolicy());
+ search(new RealmQuery.Builder().build()).getResult().getFirst().getPasswordPolicy());
UserCR userCR = new UserCR();
userCR.setRealm(SyncopeConstants.ROOT_REALM);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
index 987780c3ac..c7a8ddae5c 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -68,8 +69,166 @@ public class RealmITCase extends AbstractITCase {
@Test
public void search() {
- PagedResult<RealmTO> match = REALM_SERVICE.search(new RealmQuery.Builder().keyword("*o*").build());
+ PagedResult<RealmTO> match = REALM_SERVICE.search(new RealmQuery.Builder().fiql("name=~*o*").build());
assertTrue(match.getResult().stream().allMatch(realm -> realm.getName().contains("o")));
+
+ RealmTO realm = new RealmTO();
+ realm.setName("searchTest1");
+ realm.getAnyTypeClasses().add("other");
+ realm.getPlainAttrs().add(new Attr.Builder("ctype").value("number1").build());
+ realm.getPlainAttrs().add(new Attr.Builder("aLong").value("42").build());
+ realm.getPlainAttrs().add(new Attr.Builder("loginDate").value("2008-05-26").build());
+ realm.getResources().add(RESOURCE_NAME_LDAP_ORGUNIT);
+ REALM_SERVICE.create(SyncopeConstants.ROOT_REALM, realm);
+
+ realm = new RealmTO();
+ realm.setName("searchTest2");
+ realm.getAnyTypeClasses().add("other");
+ realm.getPlainAttrs().add(new Attr.Builder("ctype").value("string2").build());
+ realm.getPlainAttrs().add(new Attr.Builder("aLong").value("90").build());
+ realm.getPlainAttrs().add(new Attr.Builder("loginDate").value("2009-05-26").build());
+ realm.getResources().add(RESOURCE_NAME_LDAP_ORGUNIT);
+ REALM_SERVICE.create("/even", realm);
+
+ realm = new RealmTO();
+ realm.setName("searchTest3");
+ realm.getAnyTypeClasses().add("other");
+ realm.getPlainAttrs().add(new Attr.Builder("ctype").value("string2").build());
+ realm.getPlainAttrs().add(new Attr.Builder("aLong").value("42").build());
+ realm.getPlainAttrs().add(new Attr.Builder("loginDate").value("2009-05-26").value("2010-05-26").build());
+ realm.getResources().add(RESOURCE_NAME_LDAP_ORGUNIT);
+ REALM_SERVICE.create("/even", realm);
+
+ realm = new RealmTO();
+ realm.setName("searchTest4");
+ realm.getAnyTypeClasses().add("other");
+ realm.getPlainAttrs().add(new Attr.Builder("aLong").value("4242").build());
+ realm.getResources().add(RESOURCE_NAME_LDAP_ORGUNIT);
+ REALM_SERVICE.create("/even/two", realm);
+
+ realm = new RealmTO();
+ realm.setName("searchTest5");
+ realm.getAnyTypeClasses().add("other");
+ realm.getPlainAttrs().add(new Attr.Builder("ctype").value("String5").build());
+ realm.getPlainAttrs().add(new Attr.Builder("aLong").value("5").build());
+ realm.getPlainAttrs().add(new Attr.Builder("loginDate").value("2011-05-26").build());
+ realm.getResources().add(RESOURCE_NAME_LDAP_ORGUNIT);
+ REALM_SERVICE.create("/even", realm);
+
+ // Numeric equality
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("aLong==90").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().allMatch(r -> r.getName().equals("searchTest2")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().fiql("aLong==42").orderBy("name DESC").build());
+ assertEquals(2, match.getSize());
+ assertEquals("searchTest3", match.getResult().get(0).getName());
+ assertEquals("searchTest1", match.getResult().get(1).getName());
+
+ // Mixed numeric + string filters
+ match = REALM_SERVICE.search(new RealmQuery.Builder().fiql("aLong==42;ctype=~string*").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("name==searchTest*;ctype==string*").
+ build());
+ assertEquals(2, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest2")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("name==searchTest*;ctype=~string*").
+ build());
+ assertEquals(3, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest2")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest5")));
+
+ // Negated LIKE
+ match = REALM_SERVICE.search(new RealmQuery.Builder().fiql("aLong==42;ctype!~string*").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest1")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("name==searchTest*;ctype=~string5").
+ build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest5")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("aLong==4242;ctype==$null").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest4")));
+
+ // Null / not-null checks on optional plain attrs
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("name==searchTest*;ctype!=$null").
+ build());
+ assertEquals(3, match.getSize());
+ assertTrue(match.getResult().stream().noneMatch(r -> r.getName().equals("searchTest4")));
+
+ // Numeric ranges
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("name==searchTest*;aLong=gt=42").
+ build());
+ assertEquals(2, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest2")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest4")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").
+ fiql("name==searchTest*;aLong=ge=80;aLong=le=100").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest2")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("name==searchTest*;aLong=lt=42").
+ build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest5")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("name==searchTest*;aLong=le=42").
+ build());
+ assertEquals(2, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest5")));
+
+ // Grouping / OR conditions
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").
+ fiql("name==searchTest*;(aLong==90,ctype==String5)").build());
+ assertEquals(2, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest2")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest5")));
+
+ // Date equality against multivalue date schema
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("loginDate==2009-05-26").build());
+ assertEquals(2, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest2")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("loginDate==2010-05-26").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+
+ // Date range checks on multivalue schema
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").fiql("loginDate=gt=2010-01-01").build());
+ assertEquals(2, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest5")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").
+ fiql("loginDate=ge=2010-05-26;loginDate=le=2010-05-26").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest3")));
+
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").
+ fiql("name==searchTest*;loginDate==$null").build());
+ assertEquals(1, match.getSize());
+ assertTrue(match.getResult().stream().anyMatch(r -> r.getName().equals("searchTest4")));
+
+ // Date nullability checks
+ match = REALM_SERVICE.search(new RealmQuery.Builder().base("/even").
+ fiql("name==searchTest*;loginDate!=$null").build());
+ assertEquals(3, match.getSize());
+ assertTrue(match.getResult().stream().noneMatch(r -> r.getName().equals("searchTest4")));
+
+ // Validation error for numeric schema with non-numeric value
+ SyncopeClientException exception = assertThrows(SyncopeClientException.class,
+ () -> REALM_SERVICE.search(new RealmQuery.Builder().fiql("aLong==notANumber").build()));
+ assertEquals(ClientExceptionType.InvalidSearchParameters, exception.getType());
}
@Test
@@ -406,7 +565,7 @@ public class RealmITCase extends AbstractITCase {
@Test
public void issueSYNCOPE1472() {
// 1. assign twice resource-ldap-orgunit to /odd
- RealmTO realmTO = REALM_SERVICE.search(new RealmQuery.Builder().base("/odd").build()).getResult().getFirst();
+ RealmTO realmTO = getRealm("/odd").orElseThrow();
realmTO.getResources().clear();
realmTO.getResources().add("resource-ldap-orgunit");
realmTO.getResources().add("resource-ldap-orgunit");
@@ -456,7 +615,7 @@ public class RealmITCase extends AbstractITCase {
Response response = REALM_SERVICE.create(SyncopeConstants.ROOT_REALM, realmTO);
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatusInfo().getStatusCode());
childRealm = REALM_SERVICE.search(new RealmQuery.Builder().
- base(SyncopeConstants.ROOT_REALM).keyword("child").build()).getResult().getFirst();
+ base(SyncopeConstants.ROOT_REALM).fiql("name=~child").build()).getResult().getFirst();
// MANAGER CANNOT UPDATE /child
try {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
index 5b163f911c..17d52b7eb9 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserITCase.java
@@ -959,7 +959,7 @@ public class UserITCase extends AbstractITCase {
passwordPolicy = createPolicy(PolicyType.PASSWORD, passwordPolicy);
assertNotNull(passwordPolicy);
- RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().keyword("two").build()).getResult().getFirst();
+ RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().fiql("name==two").build()).getResult().getFirst();
String oldAccountPolicy = realm.getAccountPolicy();
realm.setAccountPolicy(accountPolicy.getKey());
String oldPasswordPolicy = realm.getPasswordPolicy();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
index 0b42e19f9e..e0321ed187 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/UserIssuesITCase.java
@@ -794,7 +794,7 @@ public class UserIssuesITCase extends AbstractITCase {
}
assertNotNull(logicActions);
- RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().keyword("two").build()).getResult().getFirst();
+ RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().fiql("name==two").build()).getResult().getFirst();
assertNotNull(realm);
realm.getActions().add(logicActions.getKey());
REALM_SERVICE.update(realm);
@@ -1201,7 +1201,7 @@ public class UserIssuesITCase extends AbstractITCase {
passwordPolicy = createPolicy(PolicyType.PASSWORD, passwordPolicy);
assertNotNull(passwordPolicy);
- RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().keyword("two").build()).getResult().getFirst();
+ RealmTO realm = REALM_SERVICE.search(new RealmQuery.Builder().fiql("name==two").build()).getResult().getFirst();
String oldPasswordPolicy = realm.getPasswordPolicy();
realm.setPasswordPolicy(passwordPolicy.getKey());
REALM_SERVICE.update(realm);
@@ -1378,7 +1378,8 @@ public class UserIssuesITCase extends AbstractITCase {
PasswordPolicyTO pwdPolicy = POLICY_SERVICE.read(PolicyType.PASSWORD, "ce93fcda-dc3a-4369-a7b0-a6108c261c85");
assertEquals(1, pwdPolicy.getHistoryLength());
- RealmTO evenTwo = REALM_SERVICE.search(new RealmQuery.Builder().keyword("two").build()).getResult().getFirst();
+ RealmTO evenTwo = REALM_SERVICE.search(new RealmQuery.Builder().fiql("name==two").build())
+ .getResult().getFirst();
evenTwo.setPasswordPolicy(pwdPolicy.getKey());
REALM_SERVICE.update(evenTwo);