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 2019/12/17 11:17:08 UTC

[syncope] branch master updated: [SYNCOPE-1527] Code refactoring to allow for custom search conditions, including Console support

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 e60ec59  [SYNCOPE-1527] Code refactoring to allow for custom search conditions, including Console support
e60ec59 is described below

commit e60ec59c065133ea272ddf55be93c15ba3117e4f
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Tue Dec 17 08:16:49 2019 +0100

    [SYNCOPE-1527] Code refactoring to allow for custom search conditions, including Console support
---
 .../ConnInstanceHistoryConfDirectoryPanel.java     |   2 +-
 .../console/panels/ConnObjectListViewPanel.java    |   3 +-
 .../client/console/panels/HistoryConfDetails.java  |   2 +-
 .../console/panels/RemediationDirectoryPanel.java  |  42 +--
 .../panels/ResourceHistoryConfDirectoryPanel.java  |   2 +-
 .../panels/search/ConnObjectSearchPanel.java       |  10 +-
 .../policies/ProvisioningPolicyModalPanel.java     |   2 +-
 .../status/ResourceStatusDirectoryPanel.java       |   3 +-
 .../wizards/any/LinkedAccountPlainAttrsPanel.java  |   5 +-
 .../wizards/resources/ResourceDetailsPanel.java    |  12 +-
 .../wizards/resources/ResourceMappingPanel.java    |   3 +-
 .../syncope/client/ui/commons/Constants.java       |   2 +
 .../client/ui/commons/DirectoryDataProvider.java   |   2 +-
 .../commons/layout/AbstractAnyFormBaseLayout.java  |   1 -
 .../client/console/SyncopeWebApplication.java      |  10 +-
 .../client/console/audit/HistoryAuditDetails.java  |   2 +-
 .../client/console/commons/AnyDataProvider.java    |   3 +-
 .../commons/SortableAnyProviderComparator.java     |   3 +-
 .../syncope/client/console/layout/AnyLayout.java   |  70 ++++
 .../client/console/layout/AnyLayoutUtils.java      | 207 ++++++++++++
 ...onsoleLayoutInfo.java => AnyLayoutWrapper.java} |  12 +-
 .../client/console/layout/FormLayoutInfoUtils.java | 154 ---------
 .../notifications/MailTemplateDirectoryPanel.java  |   6 +-
 .../notifications/NotificationDirectoryPanel.java  |   5 +-
 .../console/notifications/NotificationWrapper.java |   7 +-
 .../console/notifications/TemplateModal.java       |   4 +-
 .../client/console/panels/AbstractLogsPanel.java   |   2 +-
 .../console/panels/AbstractSchemaDetailsPanel.java |   6 +-
 .../syncope/client/console/panels/AnyPanel.java    | 269 ++++++++-------
 .../console/panels/AnyTypeClassDetailsPanel.java   |  11 +-
 .../client/console/panels/AnyTypeClassesPanel.java |   2 +-
 .../client/console/panels/AnyTypeDetailsPanel.java |   9 +-
 .../client/console/panels/AnyTypesPanel.java       |   2 +-
 .../console/panels/ApplicationDirectoryPanel.java  |   3 +-
 .../console/panels/ApplicationModalPanel.java      |   4 +-
 .../console/panels/DynRealmDirectoryPanel.java     |   3 +-
 .../client/console/panels/DynRealmModalPanel.java  |   4 +-
 .../client/console/panels/GroupDirectoryPanel.java | 131 ++++----
 .../panels/ImplementationDirectoryPanel.java       |   6 +-
 .../console/panels/ImplementationModalPanel.java   |   4 +-
 .../console/panels/PrivilegeDirectoryPanel.java    |   3 +-
 .../console/panels/PrivilegeWizardBuilder.java     |   4 +-
 .../syncope/client/console/panels/Realm.java       |  21 +-
 .../panels/RelationshipTypeDetailsPanel.java       |   6 +-
 .../console/panels/RelationshipTypesPanel.java     |   2 +-
 .../client/console/panels/RoleDirectoryPanel.java  |  88 ++---
 .../client/console/panels/SchemaTypePanel.java     |  24 +-
 .../console/panels/SecurityQuestionsPanel.java     |   2 +-
 .../console/panels/search/AbstractSearchPanel.java |  14 +-
 .../console/panels/search/GroupSearchPanel.java    |   4 +-
 .../client/console/panels/search/SearchClause.java |   3 +-
 .../console/panels/search/SearchClausePanel.java   | 147 ++++++---
 .../client/console/panels/search/SearchUtils.java  |  61 ++--
 .../console/panels/search/UserSearchPanel.java     |   4 +-
 .../console/policies/PolicyDirectoryPanel.java     |   2 +-
 .../console/reports/ReportDirectoryPanel.java      |   3 +-
 .../reports/ReportTemplateDirectoryPanel.java      |   6 +-
 .../client/console/rest/RoleRestClient.java        |  19 +-
 .../console/tasks/ExecutionsDirectoryPanel.java    |   7 +-
 .../tasks/NotificationTaskDirectoryPanel.java      |   2 +-
 .../tasks/PropagationTaskDirectoryPanel.java       |   2 +-
 .../tasks/ProvisioningTaskDirectoryPanel.java      |   3 +-
 .../console/tasks/SchedTaskDirectoryPanel.java     |   2 +-
 .../client/console/tasks/TaskDirectoryPanel.java   |   3 +-
 .../console/widgets/ReconDetailsModalPanel.java    |   2 +-
 .../console/widgets/ReconciliationWidget.java      |   4 +-
 .../reconciliation/ReconciliationReportParser.java |   7 +-
 .../syncope/client/console/wizards/any/Groups.java |  13 +-
 .../client/console/wizards/any/Relationships.java  |   2 +-
 .../console/wizards/role/RoleWizardBuilder.java    |   6 +-
 .../wicket/markup/html/form/JsonDiffPanel.html     |   4 +-
 .../wicket/markup/html/form/JsonEditorPanel.html   |   2 +-
 .../wicket/markup/html/form/XMLEditorPanel.html    |   2 +-
 ...ormLayoutInfoUtils.java => AnyLayoutUtils.java} |  17 +-
 .../apache/syncope/client/enduser/pages/Self.java  |  14 +-
 .../client/enduser/rest/RoleRestClient.java        |  19 +-
 .../syncope/common/lib/search/SpecialAttr.java     |   1 -
 .../common/rest/api/service/RoleService.java       |  14 +-
 .../org/apache/syncope/core/logic/RealmLogic.java  |   6 +-
 .../org/apache/syncope/core/logic/RoleLogic.java   |   8 +-
 .../apache/syncope/core/logic/SyncopeLogic.java    |  12 +-
 .../core/rest/cxf/service/AbstractServiceImpl.java |  11 +-
 .../core/rest/cxf/service/RoleServiceImpl.java     |  13 +-
 .../core/persistence/api/dao/search/AnyCond.java   |   2 +-
 .../search/{AttributeCond.java => AttrCond.java}   |   8 +-
 .../persistence/api/dao/search/SearchCond.java     | 359 ++++-----------------
 .../syncope/core/persistence/api/entity/Role.java  |   4 +-
 .../api/search/SearchCondConverter.java            |   4 +-
 .../persistence/api/search/SearchCondVisitor.java  | 157 ++++-----
 .../api/search/SearchCondConverterTest.java        | 129 ++++----
 .../jpa/dao/AbstractJPAJSONAnySearchDAO.java       |   4 +-
 .../persistence/jpa/dao/JPAJSONPlainSchemaDAO.java |   2 +-
 .../persistence/jpa/dao/MyJPAJSONAnySearchDAO.java |  20 +-
 .../persistence/jpa/dao/PGJPAJSONAnySearchDAO.java |  20 +-
 .../main/resources/myjson/persistence.properties   |   1 +
 .../main/resources/pgjsonb/persistence.properties  |   1 +
 .../src/test/resources/domains/MasterContent.xml   |  51 +--
 .../core/persistence/jpa/PersistenceContext.java   |  10 +
 .../core/persistence/jpa/dao/AbstractAnyDAO.java   |   6 +-
 .../persistence/jpa/dao/AbstractAnySearchDAO.java  |  24 +-
 .../jpa/dao/DefaultPullCorrelationRule.java        |  18 +-
 .../core/persistence/jpa/dao/JPAAnyMatchDAO.java   | 133 +++++---
 .../core/persistence/jpa/dao/JPAAnySearchDAO.java  | 176 +++++-----
 .../core/persistence/jpa/dao/JPADynRealmDAO.java   |   9 +-
 .../core/persistence/jpa/dao/JPAGroupDAO.java      |  10 +-
 .../core/persistence/jpa/dao/JPARoleDAO.java       |  11 +-
 .../core/persistence/jpa/entity/JPARole.java       |  10 +-
 .../src/main/resources/persistence.properties      |   1 +
 .../core/persistence/jpa/inner/AnyMatchTest.java   |  30 +-
 .../core/persistence/jpa/inner/AnySearchTest.java  | 178 +++++-----
 .../core/persistence/jpa/outer/AnySearchTest.java  |  22 +-
 .../core/persistence/jpa/outer/DynRealmTest.java   |   4 +-
 .../src/test/resources/domains/MasterContent.xml   |  50 +--
 .../java/data/DynRealmDataBinderImpl.java          |   6 +-
 .../java/data/GroupDataBinderImpl.java             |   6 +-
 .../provisioning/java/data/RoleDataBinderImpl.java |   6 +-
 .../job/GroupMemberProvisionTaskJobDelegate.java   |   4 +-
 .../java/job/report/GroupReportlet.java            |   8 +-
 .../java/job/report/ReconciliationReportlet.java   |  16 +-
 .../java/job/report/UserReportlet.java             |   8 +-
 .../notification/DefaultNotificationManager.java   |  12 +-
 .../pushpull/DefaultRealmPullResultHandler.java    |   6 +-
 .../provisioning/java/pushpull/InboundMatcher.java |  14 +-
 .../java/pushpull/PushJobDelegate.java             |   6 +-
 .../core/spring/security/AuthDataAccessor.java     |   6 +-
 .../src/main/resources/persistence.properties.all  |   1 +
 .../main/resources/persistence.properties.myjson   |   1 +
 .../main/resources/persistence.properties.pgjsonb  |   1 +
 .../jpa/dao/ElasticsearchAnySearchDAO.java         | 131 +++++---
 .../src/main/resources/persistence.properties      |   1 +
 .../panels/UserRequestFormDirectoryPanel.java      |   4 +-
 .../apache/syncope/core/logic/SCIMDataBinder.java  |   2 +-
 .../core/logic/scim/SearchCondConverter.java       |   4 +-
 .../syncope/core/logic/scim/SearchCondVisitor.java | 158 ++++-----
 .../syncope/core/logic/scim/SCIMFilterTest.java    | 150 ++++-----
 .../ext/scimv2/cxf/service/AbstractService.java    |   2 +-
 .../ext/scimv2/cxf/service/GroupServiceImpl.java   |   2 +-
 .../reference/flowable/PrintersValueProvider.java  |   2 +-
 .../fit/core/reference/CustomJWTSSOProvider.java   |   8 +-
 .../LinkedAccountSamplePullCorrelationRule.java    |   8 +-
 .../resources/elasticsearch/persistence.properties |   1 +
 .../org/apache/syncope/fit/AbstractITCase.java     |  12 +-
 .../src/test/resources/TestPullRule.groovy         |   8 +-
 .../systemadministration/dbms.adoc                 |   2 +
 144 files changed, 1932 insertions(+), 1737 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java
index e614e70..abeea09 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnInstanceHistoryConfDirectoryPanel.java
@@ -89,7 +89,7 @@ public abstract class ConnInstanceHistoryConfDirectoryPanel extends DirectoryPan
         final List<IColumn<ConnInstanceHistoryConfTO, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
 
         columns.add(new PropertyColumn<>(new StringResourceModel(
                 "creator", this), "creator", "creator"));
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 193d6b5..3e959c0 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
@@ -319,6 +319,7 @@ public abstract class ConnObjectListViewPanel extends Panel {
         return SearchUtils.buildFIQL(
                 searchPanel.getModel().getObject(),
                 SyncopeClient.getConnObjectTOFiqlSearchConditionBuilder(),
-                searchPanel.getAvailableSchemaTypes());
+                searchPanel.getAvailableSchemaTypes(),
+                SearchUtils.NO_CUSTOM_CONDITION);
     }
 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfDetails.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfDetails.java
index 9af04c7..d40369e 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfDetails.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/HistoryConfDetails.java
@@ -168,7 +168,7 @@ public class HistoryConfDetails<T extends AbstractHistoryConf> extends Multileve
         final AjaxDropDownChoicePanel<String> dropdownElem = new AjaxDropDownChoicePanel<>(
                 "compareDropdown",
                 getString("compare"),
-                new PropertyModel<>(selectedHistoryConfTO, "key"),
+                new PropertyModel<>(selectedHistoryConfTO, Constants.KEY_FIELD_NAME),
                 false);
         dropdownElem.setChoices(keys);
         dropdownElem.setChoiceRenderer(new IChoiceRenderer<String>() {
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java
index fe488fb..71ac2eb 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/RemediationDirectoryPanel.java
@@ -29,8 +29,8 @@ import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.commons.IdMConstants;
+import org.apache.syncope.client.console.layout.AnyLayoutUtils;
 import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo;
-import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
 import org.apache.syncope.client.console.layout.GroupFormLayoutInfo;
 import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.console.pages.BasePage;
@@ -105,7 +105,7 @@ public class RemediationDirectoryPanel
         List<IColumn<RemediationTO, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(
                 new ResourceModel("operation"), "operation", "operation"));
         columns.add(new PropertyColumn<>(
@@ -208,12 +208,12 @@ public class RemediationDirectoryPanel
                             AjaxWizard.EditItemActionEvent<UserTO> userEvent =
                                     new AjaxWizard.EditItemActionEvent<>(newUserTO, target);
                             userEvent.forceModalPanel(new RemediationUserWizardBuilder(
-                                model.getObject(),
-                                previousUserTO,
-                                newUserTO,
-                                AnyTypeRestClient.read(remediationTO.getAnyType()).getClasses(),
-                                FormLayoutInfoUtils.fetch(List.of(remediationTO.getAnyType())).getLeft(),
-                                pageRef
+                                    model.getObject(),
+                                    previousUserTO,
+                                    newUserTO,
+                                    AnyTypeRestClient.read(remediationTO.getAnyType()).getClasses(),
+                                    AnyLayoutUtils.fetch(List.of(remediationTO.getAnyType())).getUser(),
+                                    pageRef
                             ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT));
                             send(RemediationDirectoryPanel.this, Broadcast.EXACT, userEvent);
                             break;
@@ -235,12 +235,12 @@ public class RemediationDirectoryPanel
                             AjaxWizard.EditItemActionEvent<GroupTO> groupEvent =
                                     new AjaxWizard.EditItemActionEvent<>(newGroupTO, target);
                             groupEvent.forceModalPanel(new RemediationGroupWizardBuilder(
-                                model.getObject(),
-                                previousGroupTO,
-                                newGroupTO,
-                                AnyTypeRestClient.read(remediationTO.getAnyType()).getClasses(),
-                                FormLayoutInfoUtils.fetch(List.of(remediationTO.getAnyType())).getMiddle(),
-                                pageRef
+                                    model.getObject(),
+                                    previousGroupTO,
+                                    newGroupTO,
+                                    AnyTypeRestClient.read(remediationTO.getAnyType()).getClasses(),
+                                    AnyLayoutUtils.fetch(List.of(remediationTO.getAnyType())).getGroup(),
+                                    pageRef
                             ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT));
                             send(RemediationDirectoryPanel.this, Broadcast.EXACT, groupEvent);
                             break;
@@ -262,13 +262,13 @@ public class RemediationDirectoryPanel
                             AjaxWizard.EditItemActionEvent<AnyObjectTO> anyObjectEvent =
                                     new AjaxWizard.EditItemActionEvent<>(newAnyObjectTO, target);
                             anyObjectEvent.forceModalPanel(new RemediationAnyObjectWizardBuilder(
-                                model.getObject(),
-                                previousAnyObjectTO,
-                                newAnyObjectTO,
-                                AnyTypeRestClient.read(remediationTO.getAnyType()).getClasses(),
-                                FormLayoutInfoUtils.fetch(List.of(remediationTO.getAnyType())).
-                                    getRight().values().iterator().next(),
-                                pageRef
+                                    model.getObject(),
+                                    previousAnyObjectTO,
+                                    newAnyObjectTO,
+                                    AnyTypeRestClient.read(remediationTO.getAnyType()).getClasses(),
+                                    AnyLayoutUtils.fetch(List.of(remediationTO.getAnyType())).getAnyObjects().
+                                            get(remediationTO.getAnyType()),
+                                    pageRef
                             ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT));
                             send(RemediationDirectoryPanel.this, Broadcast.EXACT, anyObjectEvent);
                     }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java
index 60d48c2..4e67467 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceHistoryConfDirectoryPanel.java
@@ -90,7 +90,7 @@ public abstract class ResourceHistoryConfDirectoryPanel extends DirectoryPanel<
         final List<IColumn<ResourceHistoryConfTO, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
 
         columns.add(new PropertyColumn<>(new StringResourceModel(
                 "creator", this), "creator", "creator"));
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java
index 918eced..833baf1 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/search/ConnObjectSearchPanel.java
@@ -34,7 +34,7 @@ public class ConnObjectSearchPanel extends AbstractSearchPanel {
 
     private static final long serialVersionUID = 21020550706646L;
 
-    private final ConnectorRestClient connectorRestClient = new ConnectorRestClient();
+    protected final ConnectorRestClient connectorRestClient = new ConnectorRestClient();
 
     protected final ResourceTO resource;
 
@@ -42,14 +42,18 @@ public class ConnObjectSearchPanel extends AbstractSearchPanel {
 
         private static final long serialVersionUID = 6308997285778809578L;
 
-        protected final ResourceTO resource;
+        private final ResourceTO resource;
 
         private final AnyTypeKind anyType;
 
         private final String typeName;
 
-        public Builder(final ResourceTO resource, final AnyTypeKind anyType, final String type,
+        public Builder(
+                final ResourceTO resource,
+                final AnyTypeKind anyType,
+                final String type,
                 final IModel<List<SearchClause>> model) {
+
             super(model);
             this.resource = resource;
             this.anyType = anyType;
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java
index 2ebb777..5f0b2f9 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/policies/ProvisioningPolicyModalPanel.java
@@ -324,7 +324,7 @@ public class ProvisioningPolicyModalPanel extends AbstractModalPanel<Provisionin
                             ? AnyTypeKind.GROUP
                             : AnyTypeKind.ANY_OBJECT).stream().map(EntityTO::getKey).
                             collect(Collectors.toList());
-            choices.add("key");
+            choices.add(Constants.KEY_FIELD_NAME);
             choices.add(rule.getAnyType().equals(AnyTypeKind.USER.name()) ? "username" : "name");
             Collections.sort(choices);
             return choices;
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java
index 2ec8b9d..c85f488 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/status/ResourceStatusDirectoryPanel.java
@@ -40,6 +40,7 @@ import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.Bas
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.panels.ModalPanel;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.search.AbstractFiqlSearchConditionBuilder;
@@ -255,7 +256,7 @@ public class ResourceStatusDirectoryPanel
                         bld = SyncopeClient.getAnyObjectSearchConditionBuilder(type);
                         restClient = new AnyObjectRestClient();
                 }
-                fiql = bld.isNotNull("key").query();
+                fiql = bld.isNotNull(Constants.KEY_FIELD_NAME).query();
             }
         }
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java
index cac1afb..edfbed7 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/any/LinkedAccountPlainAttrsPanel.java
@@ -30,7 +30,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
-import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
 import org.apache.syncope.common.lib.EntityTOUtils;
@@ -46,6 +45,7 @@ import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
 import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.client.console.layout.AnyLayoutUtils;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
@@ -80,7 +80,7 @@ public class LinkedAccountPlainAttrsPanel extends AbstractAttrsWizardStep<PlainS
         super(userTO,
                 AjaxWizard.Mode.EDIT,
                 new AnyTypeRestClient().read(userTO.getType()).getClasses(),
-                FormLayoutInfoUtils.fetch(Arrays.asList(userTO.getType())).getLeft().getWhichPlainAttrs(),
+                AnyLayoutUtils.fetch(Arrays.asList(userTO.getType())).getUser().getWhichPlainAttrs(),
                 modelObject);
 
         this.linkedAccountTO = modelObject.getInnerObject();
@@ -251,5 +251,4 @@ public class LinkedAccountPlainAttrsPanel extends AbstractAttrsWizardStep<PlainS
             });
         }
     }
-
 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
index d4f08c2..f326f76 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.syncope.client.console.rest.ImplementationRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
@@ -65,10 +66,9 @@ public class ResourceDetailsPanel extends WizardStep {
         add(container);
 
         container.add(new AjaxTextFieldPanel(
-                "key",
-                new ResourceModel("key", "key").
-                        getObject(),
-                new PropertyModel<>(resourceTO, "key"),
+                Constants.KEY_FIELD_NAME,
+                new ResourceModel(Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME).getObject(),
+                new PropertyModel<>(resourceTO, Constants.KEY_FIELD_NAME),
                 false).addRequiredLabel().setEnabled(createFlag));
 
         container.add(new AjaxCheckBoxPanel(
@@ -81,7 +81,7 @@ public class ResourceDetailsPanel extends WizardStep {
                 "propagationPriority",
                 "propagationPriority",
                 Integer.class,
-                new PropertyModel<Integer>(resourceTO, "propagationPriority")));
+                new PropertyModel<>(resourceTO, "propagationPriority")));
 
         container.add(new AjaxCheckBoxPanel("randomPwdIfNotProvided",
                 new ResourceModel("randomPwdIfNotProvided", "randomPwdIfNotProvided").getObject(),
@@ -91,7 +91,7 @@ public class ResourceDetailsPanel extends WizardStep {
         container.add(new AjaxPalettePanel.Builder<String>().
                 setAllowMoveAll(true).setAllowOrder(true).
                 build("propagationActions",
-                        new PropertyModel<List<String>>(resourceTO, "propagationActions"),
+                        new PropertyModel<>(resourceTO, "propagationActions"),
                         new ListModel<>(propagationActions.getObject())).
                 setOutputMarkupId(true));
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java
index 444b1b5..c7381ed 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceMappingPanel.java
@@ -27,6 +27,7 @@ import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.rest.ConnectorRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
@@ -125,7 +126,7 @@ public class ResourceMappingPanel extends AbstractMappingPanel {
 
         Set<String> choices = new HashSet<>();
         if (SyncopeConstants.REALM_ANYTYPE.equals(provision.getAnyType())) {
-            choices.add("key");
+            choices.add(Constants.KEY_FIELD_NAME);
             choices.add("name");
             choices.add("fullpath");
         } else {
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java
index 4f3a090..7a0e0ac 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java
@@ -105,6 +105,8 @@ public final class Constants {
 
     public static final String NOTIFICATION_LEVEL_PARAM = "notificationLevel";
 
+    public static final String ENDUSER_ANYLAYOUT = "enduser.anylayout";
+
     public static Component getJEXLPopover(final Component caller, final TooltipConfig.Placement placement) {
         return getJEXLPopover(caller, placement, caller.getString("jexl_ex1"), caller.getString("jexl_ex2"));
     }
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/DirectoryDataProvider.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/DirectoryDataProvider.java
index fad6184..49c5d9d 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/DirectoryDataProvider.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/DirectoryDataProvider.java
@@ -33,6 +33,6 @@ public abstract class DirectoryDataProvider<T extends Serializable> extends Sort
         this.paginatorRows = paginatorRows;
 
         // default sorting
-        setSort("key", SortOrder.ASCENDING);
+        setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
     }
 }
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/layout/AbstractAnyFormBaseLayout.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/layout/AbstractAnyFormBaseLayout.java
index 83b6421..cdf465e 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/layout/AbstractAnyFormBaseLayout.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/layout/AbstractAnyFormBaseLayout.java
@@ -97,5 +97,4 @@ public abstract class AbstractAnyFormBaseLayout<A extends AnyTO, F extends AnyFo
     public void setResources(final boolean resources) {
         this.resources = resources;
     }
-
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
index e0e1253..4ad45e6 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
@@ -146,18 +146,18 @@ public class SyncopeWebApplication extends WicketBootSecuredWebApplication {
     protected void populatePageClasses(final Properties props) {
         Enumeration<String> propNames = (Enumeration<String>) props.propertyNames();
         while (propNames.hasMoreElements()) {
-            String name = propNames.nextElement();
-            if (name.startsWith("page.")) {
+            String className = propNames.nextElement();
+            if (className.startsWith("page.")) {
                 try {
-                    Class<?> clazz = ClassUtils.getClass(props.getProperty(name));
+                    Class<?> clazz = ClassUtils.getClass(props.getProperty(className));
                     if (BasePage.class.isAssignableFrom(clazz)) {
                         pageClasses.put(
-                                StringUtils.substringAfter("page.", name), (Class<? extends BasePage>) clazz);
+                                StringUtils.substringAfter("page.", className), (Class<? extends BasePage>) clazz);
                     } else {
                         LOG.warn("{} does not extend {}, ignoring...", clazz.getName(), BasePage.class.getName());
                     }
                 } catch (ClassNotFoundException e) {
-                    LOG.error("While looking for class identified by property '{}'", name, e);
+                    LOG.error("While looking for class identified by property '{}'", className, e);
                 }
             }
         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/HistoryAuditDetails.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/HistoryAuditDetails.java
index 672f982..ee1d4b2 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/HistoryAuditDetails.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/HistoryAuditDetails.java
@@ -165,7 +165,7 @@ public class HistoryAuditDetails extends MultilevelPanel.SecondLevel {
         final AjaxDropDownChoicePanel<String> dropdownElem = new AjaxDropDownChoicePanel<>(
                 "compareDropdown",
                 getString("compare"),
-                new PropertyModel<>(selected, "key"),
+                new PropertyModel<>(selected, Constants.KEY_FIELD_NAME),
                 false);
         dropdownElem.setChoices(keys);
         dropdownElem.setChoiceRenderer(new IChoiceRenderer<String>() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java
index cfefd8a..61a723d 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/AnyDataProvider.java
@@ -27,6 +27,7 @@ import java.util.Optional;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.rest.AbstractAnyRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -82,7 +83,7 @@ public class AnyDataProvider<A extends AnyTO> extends DirectoryDataProvider<A> {
                 break;
 
             default:
-                setSort("key", SortOrder.ASCENDING);
+                setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
         }
 
         this.comparator = new SortableAnyProviderComparator<>(this);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java
index be9e144..2e9bc28 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/SortableAnyProviderComparator.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.commons;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.types.SchemaType;
@@ -32,7 +33,7 @@ public class SortableAnyProviderComparator<T extends AnyTO> extends SortableData
     private static final long serialVersionUID = 1775967163571699258L;
 
     private static final Set<String> INLINE_PROPS = Set.of(
-            new String[] { "key", "status", "token", "username", "name" });
+            Constants.KEY_FIELD_NAME, "status", "token", "username", "name");
 
     public SortableAnyProviderComparator(final SortableDataProvider<T, String> provider) {
         super(provider);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayout.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayout.java
new file mode 100644
index 0000000..1978f78
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayout.java
@@ -0,0 +1,70 @@
+/*
+ * 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.client.console.layout;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.syncope.client.console.panels.AnyPanel;
+
+public class AnyLayout implements Serializable {
+
+    private static final long serialVersionUID = 488645029994410970L;
+
+    private String anyPanelClass = AnyPanel.class.getName();
+
+    @JsonProperty("USER")
+    private UserFormLayoutInfo user;
+
+    @JsonProperty("GROUP")
+    private GroupFormLayoutInfo group;
+
+    private final Map<String, AnyObjectFormLayoutInfo> anyObjects = new HashMap<>();
+
+    public String getAnyPanelClass() {
+        return anyPanelClass;
+    }
+
+    public void setAnyPanelClass(final String anyPanelClass) {
+        this.anyPanelClass = anyPanelClass;
+    }
+
+    public UserFormLayoutInfo getUser() {
+        return user;
+    }
+
+    public void setUser(final UserFormLayoutInfo user) {
+        this.user = user;
+    }
+
+    public GroupFormLayoutInfo getGroup() {
+        return group;
+    }
+
+    public void setGroup(final GroupFormLayoutInfo group) {
+        this.group = group;
+    }
+
+    @JsonAnyGetter
+    public Map<String, AnyObjectFormLayoutInfo> getAnyObjects() {
+        return anyObjects;
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutUtils.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutUtils.java
new file mode 100644
index 0000000..daf329f
--- /dev/null
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutUtils.java
@@ -0,0 +1,207 @@
+/*
+ * 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.client.console.layout;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.panels.AnyPanel;
+import org.apache.syncope.client.console.rest.RoleRestClient;
+import org.apache.syncope.client.ui.commons.layout.AbstractAnyFormLayout;
+import org.apache.syncope.client.ui.commons.wizards.any.AnyForm;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.AnyTypeTO;
+import org.apache.syncope.common.lib.to.RealmTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.PageReference;
+import org.springframework.util.ClassUtils;
+
+public final class AnyLayoutUtils {
+
+    private static final RoleRestClient ROLE_REST_CLIENT = new RoleRestClient();
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static void setUserIfEmpty(final AnyLayout anyLayout) {
+        if (anyLayout.getUser() == null) {
+            anyLayout.setUser(new UserFormLayoutInfo());
+        }
+    }
+
+    private static void setGroupIfEmpty(final AnyLayout anyLayout) {
+        if (anyLayout.getGroup() == null) {
+            anyLayout.setGroup(new GroupFormLayoutInfo());
+        }
+    }
+
+    private static void setAnyObjectsIfEmpty(final AnyLayout anyLayout, final List<String> anyTypes) {
+        if (anyLayout.getAnyObjects().isEmpty()) {
+            anyLayout.getAnyObjects().putAll(anyTypes.stream().filter(
+                    anyType -> !anyType.equals(AnyTypeKind.USER.name()) && !anyType.equals(AnyTypeKind.GROUP.name())).
+                    collect(Collectors.toMap(Function.identity(), anyType -> new AnyObjectFormLayoutInfo())));
+        }
+    }
+
+    private static AnyLayout empty(final List<String> anyTypes) {
+        AnyLayout anyLayout = new AnyLayout();
+        setUserIfEmpty(anyLayout);
+        setGroupIfEmpty(anyLayout);
+        setAnyObjectsIfEmpty(anyLayout, anyTypes);
+        return anyLayout;
+    }
+
+    public static AnyLayout fetch(final List<String> anyTypes) {
+        List<String> ownedRoles = SyncopeConsoleSession.get().getSelfTO().getRoles();
+        try {
+            AnyLayout anyLayout = null;
+            for (int i = 0; i < ownedRoles.size() && anyLayout == null; i++) {
+                String anyLayoutJSON = ROLE_REST_CLIENT.readAnyLayout(ownedRoles.get(i));
+                if (StringUtils.isNotBlank(anyLayoutJSON)) {
+                    anyLayout = MAPPER.readValue(anyLayoutJSON, AnyLayout.class);
+                }
+            }
+
+            if (anyLayout == null) {
+                anyLayout = empty(anyTypes);
+            }
+            setUserIfEmpty(anyLayout);
+            setGroupIfEmpty(anyLayout);
+            setAnyObjectsIfEmpty(anyLayout, anyTypes);
+
+            return anyLayout;
+        } catch (IOException e) {
+            throw new IllegalArgumentException("While parsing console layout for "
+                    + SyncopeConsoleSession.get().getSelfTO().getUsername(), e);
+        }
+    }
+
+    public static String defaultIfEmpty(final String content, final List<String> anyTypes) {
+        String result;
+
+        if (StringUtils.isBlank(content)) {
+            AnyLayout anyLayout = empty(anyTypes);
+
+            try {
+                result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(anyLayout);
+            } catch (IOException e) {
+                throw new IllegalArgumentException("While generating default console layout for "
+                        + SyncopeConsoleSession.get().getSelfTO().getUsername(), e);
+            }
+        } else {
+            try {
+                result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(MAPPER.readTree(content));
+            } catch (IOException e) {
+                result = content;
+            }
+        }
+
+        return result;
+    }
+
+    public static <A extends AnyTO, F extends AnyForm<A>, FL extends AbstractAnyFormLayout<A, F>> F newLayoutInfo(
+            final A anyTO,
+            final List<String> anyTypeClasses,
+            final FL anyFormLayout,
+            final PageReference pageRef) {
+
+        try {
+            if (anyTO instanceof UserTO) {
+                return anyFormLayout.getFormClass().getConstructor(
+                        anyTO.getClass(), // previous
+                        anyTO.getClass(), // actual
+                        List.class,
+                        anyFormLayout.getClass(),
+                        pageRef.getClass()).
+                        newInstance(null, anyTO, anyTypeClasses, anyFormLayout, pageRef);
+            } else {
+                return anyFormLayout.getFormClass().getConstructor(
+                        anyTO.getClass(), // actual
+                        List.class,
+                        anyFormLayout.getClass(),
+                        pageRef.getClass()).
+                        newInstance(anyTO, anyTypeClasses, anyFormLayout, pageRef);
+            }
+        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
+                | IllegalArgumentException | InvocationTargetException e) {
+            throw new IllegalArgumentException("Could not instantiate " + anyFormLayout.getFormClass().getName(), e);
+        }
+    }
+
+    public static <AP extends AnyPanel> AP newAnyPanel(
+            final String panelClass,
+            final String id,
+            final AnyTypeTO anyTypeTO,
+            final RealmTO realmTO,
+            final AnyLayout anyLayout,
+            final boolean enableSearch,
+            final PageReference pageRef) {
+
+        try {
+            @SuppressWarnings("unchecked")
+            Class<AP> clazz = (Class<AP>) ClassUtils.forName(panelClass, ClassUtils.getDefaultClassLoader());
+            return clazz.getConstructor(
+                    String.class,
+                    AnyTypeTO.class,
+                    RealmTO.class,
+                    AnyLayout.class,
+                    boolean.class,
+                    PageReference.class).
+                    newInstance(id, anyTypeTO, realmTO, anyLayout, enableSearch, pageRef);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Could not instantiate " + panelClass, e);
+        }
+    }
+
+    public static <AP extends AnyPanel> AP newAnyPanel(
+            final String panelClass,
+            final String id,
+            final AnyTypeTO anyTypeTO,
+            final RealmTO realmTO,
+            final AnyLayout anyLayout,
+            final boolean enableSearch,
+            final AnyPanel.DirectoryPanelSupplier directoryPanelSupplier,
+            final PageReference pageRef) {
+
+        try {
+            @SuppressWarnings("unchecked")
+            Class<AP> clazz = (Class<AP>) ClassUtils.forName(panelClass, ClassUtils.getDefaultClassLoader());
+            return clazz.getConstructor(
+                    String.class,
+                    AnyTypeTO.class,
+                    RealmTO.class,
+                    AnyLayout.class,
+                    boolean.class,
+                    AnyPanel.DirectoryPanelSupplier.class,
+                    PageReference.class).
+                    newInstance(id, anyTypeTO, realmTO, anyLayout, enableSearch, directoryPanelSupplier, pageRef);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Could not instantiate " + panelClass, e);
+        }
+    }
+
+    private AnyLayoutUtils() {
+        // private constructor for static utility class
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/ConsoleLayoutInfo.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutWrapper.java
similarity index 75%
rename from client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/ConsoleLayoutInfo.java
rename to client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutWrapper.java
index 883dc42..e3d7e86 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/ConsoleLayoutInfo.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/AnyLayoutWrapper.java
@@ -19,18 +19,18 @@
 package org.apache.syncope.client.console.layout;
 
 import java.io.Serializable;
-import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 
-public class ConsoleLayoutInfo implements Serializable {
+public class AnyLayoutWrapper implements Serializable {
 
     private static final long serialVersionUID = 961267717148831831L;
 
     private final String key;
 
-    private String content;
+    private final String content;
 
-    public ConsoleLayoutInfo(final String key) {
+    public AnyLayoutWrapper(final String key, final String content) {
         this.key = key;
+        this.content = content;
     }
 
     public String getKey() {
@@ -40,8 +40,4 @@ public class ConsoleLayoutInfo implements Serializable {
     public String getContent() {
         return content;
     }
-
-    public void setContent(final String content) {
-        this.content = FormLayoutInfoUtils.defaultConsoleLayoutInfoIfEmpty(content, AnyTypeRestClient.list());
-    }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/FormLayoutInfoUtils.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/FormLayoutInfoUtils.java
deleted file mode 100644
index c34c901..0000000
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/layout/FormLayoutInfoUtils.java
+++ /dev/null
@@ -1,154 +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.client.console.layout;
-
-import org.apache.syncope.client.ui.commons.layout.AbstractAnyFormLayout;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyForm;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Triple;
-import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.rest.RoleRestClient;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.wicket.PageReference;
-
-/**
- * Utility methods for dealing with form layout information.
- */
-public final class FormLayoutInfoUtils {
-
-    private static final ObjectMapper MAPPER = new ObjectMapper();
-
-    public static Triple<UserFormLayoutInfo, GroupFormLayoutInfo, Map<String, AnyObjectFormLayoutInfo>> fetch(
-            final Collection<String> anyTypes) {
-
-        List<String> ownedRoles = SyncopeConsoleSession.get().getSelfTO().getRoles();
-        try {
-            JsonNode tree = null;
-            for (int i = 0; i < ownedRoles.size() && tree == null; i++) {
-                String consoleLayoutInfo = RoleRestClient.readConsoleLayoutInfo(ownedRoles.get(i));
-                if (StringUtils.isNotBlank(consoleLayoutInfo)) {
-                    tree = MAPPER.readTree(consoleLayoutInfo);
-                }
-            }
-            if (tree == null) {
-                tree = MAPPER.createObjectNode();
-            }
-
-            UserFormLayoutInfo userFormLayoutInfo = tree.has(AnyTypeKind.USER.name())
-                    ? MAPPER.treeToValue(tree.get(AnyTypeKind.USER.name()), UserFormLayoutInfo.class)
-                    : new UserFormLayoutInfo();
-
-            GroupFormLayoutInfo groupFormLayoutInfo = tree.has(AnyTypeKind.USER.name())
-                    ? MAPPER.treeToValue(tree.get(AnyTypeKind.GROUP.name()), GroupFormLayoutInfo.class)
-                    : new GroupFormLayoutInfo();
-
-            Map<String, AnyObjectFormLayoutInfo> anyObjectFormLayoutInfos = new HashMap<>();
-            for (String anyType : anyTypes) {
-                if (!anyType.equals(AnyTypeKind.USER.name()) && !anyType.equals(AnyTypeKind.GROUP.name())) {
-
-                    anyObjectFormLayoutInfos.put(
-                            anyType,
-                            tree.has(anyType)
-                            ? MAPPER.treeToValue(tree.get(anyType), AnyObjectFormLayoutInfo.class)
-                            : new AnyObjectFormLayoutInfo());
-                }
-            }
-
-            return Triple.of(userFormLayoutInfo, groupFormLayoutInfo, anyObjectFormLayoutInfos);
-        } catch (IOException e) {
-            throw new IllegalArgumentException("While parsing console layout info for "
-                    + SyncopeConsoleSession.get().getSelfTO().getUsername(), e);
-        }
-    }
-
-    public static String defaultConsoleLayoutInfoIfEmpty(final String content, final List<String> anyTypes) {
-        String result;
-
-        if (StringUtils.isBlank(content)) {
-            try {
-                ObjectNode tree = MAPPER.createObjectNode();
-
-                tree.set(AnyTypeKind.USER.name(), MAPPER.valueToTree(new UserFormLayoutInfo()));
-                tree.set(AnyTypeKind.GROUP.name(), MAPPER.valueToTree(new GroupFormLayoutInfo()));
-                for (String anyType : anyTypes) {
-                    if (!anyType.equals(AnyTypeKind.USER.name()) && !anyType.equals(AnyTypeKind.GROUP.name())) {
-                        tree.set(anyType, MAPPER.valueToTree(new AnyObjectFormLayoutInfo()));
-                    }
-                }
-
-                result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(tree);
-            } catch (IOException e) {
-                throw new IllegalArgumentException("While generating default console layout info for "
-                        + SyncopeConsoleSession.get().getSelfTO().getUsername(), e);
-            }
-        } else {
-            try {
-                result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(MAPPER.readTree(content));
-            } catch (IOException e) {
-                result = content;
-            }
-        }
-
-        return result;
-    }
-
-    public static <A extends AnyTO, F extends AnyForm<A>, FL extends AbstractAnyFormLayout<A, F>> F instantiate(
-            final A anyTO,
-            final List<String> anyTypeClasses,
-            final FL anyFormLayout,
-            final PageReference pageRef) {
-
-        try {
-            if (anyTO instanceof UserTO) {
-                return anyFormLayout.getFormClass().getConstructor(
-                        anyTO.getClass(), // previous
-                        anyTO.getClass(), // actual
-                        List.class,
-                        anyFormLayout.getClass(),
-                        pageRef.getClass()).
-                        newInstance(null, anyTO, anyTypeClasses, anyFormLayout, pageRef);
-            } else {
-                return anyFormLayout.getFormClass().getConstructor(
-                        anyTO.getClass(), // actual
-                        List.class,
-                        anyFormLayout.getClass(),
-                        pageRef.getClass()).
-                        newInstance(anyTO, anyTypeClasses, anyFormLayout, pageRef);
-            }
-        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
-                | IllegalArgumentException | InvocationTargetException e) {
-            throw new IllegalArgumentException("Could not instantiate " + anyFormLayout.getFormClass().getName(), e);
-        }
-    }
-
-    private FormLayoutInfoUtils() {
-        // private constructor for static utility class
-    }
-}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java
index c5ae91e..fa8b942 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java
@@ -109,7 +109,9 @@ public class MailTemplateDirectoryPanel
     @Override
     protected List<IColumn<MailTemplateTO, String>> getColumns() {
         List<IColumn<MailTemplateTO, String>> columns = new ArrayList<>();
-        columns.add(new PropertyColumn<>(new StringResourceModel("key", this), "key", "key"));
+        columns.add(new PropertyColumn<>(
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this),
+                Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
         return columns;
     }
 
@@ -198,7 +200,7 @@ public class MailTemplateDirectoryPanel
 
         public MailTemplateProvider(final int paginatorRows) {
             super(paginatorRows);
-            setSort("key", SortOrder.ASCENDING);
+            setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
             comparator = new SortableDataProviderComparator<>(this);
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
index adad247..ec11188 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
@@ -84,7 +84,8 @@ public class NotificationDirectoryPanel
     @Override
     protected List<IColumn<NotificationTO, String>> getColumns() {
         List<IColumn<NotificationTO, String>> columns = new ArrayList<>();
-        columns.add(new KeyPropertyColumn<>(new StringResourceModel("key", this), "key"));
+        columns.add(new KeyPropertyColumn<>(
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(
                 new StringResourceModel("sender", this), "sender", "sender"));
         columns.add(new PropertyColumn<>(
@@ -175,7 +176,7 @@ public class NotificationDirectoryPanel
         public NotificationProvider(final int paginatorRows) {
             super(paginatorRows);
 
-            setSort("key", SortOrder.ASCENDING);
+            setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
             comparator = new SortableDataProviderComparator<>(this);
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java
index b80f98f..0e407c5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWrapper.java
@@ -19,10 +19,10 @@
 package org.apache.syncope.client.console.notifications;
 
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.console.panels.search.SearchClause;
@@ -51,9 +51,8 @@ public class NotificationWrapper implements Serializable {
 
     public List<Pair<String, List<SearchClause>>> getAboutClauses() {
         if (this.aboutClauses == null) {
-            this.aboutClauses = new ArrayList<>();
-            SearchUtils.getSearchClauses(this.notificationTO.getAbouts()).entrySet().
-                    forEach(entry -> this.aboutClauses.add(Pair.of(entry.getKey(), (entry.getValue()))));
+            this.aboutClauses = SearchUtils.getSearchClauses(this.notificationTO.getAbouts()).entrySet().stream().
+                    map(entry -> Pair.of(entry.getKey(), entry.getValue())).collect(Collectors.toList());
         }
 
         return this.aboutClauses;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java
index 233ecfc..4b145f9 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/notifications/TemplateModal.java
@@ -50,7 +50,9 @@ public class TemplateModal<T extends EntityTO, F> extends AbstractModalPanel<T>
         this.templateTO = templateTO;
 
         AjaxTextFieldPanel key = new AjaxTextFieldPanel(
-                "key", "key", new PropertyModel<>(templateTO, "key"), false);
+                Constants.KEY_FIELD_NAME,
+                Constants.KEY_FIELD_NAME,
+                new PropertyModel<>(templateTO, Constants.KEY_FIELD_NAME), false);
         key.setOutputMarkupPlaceholderTag(true);
         add(key.setRenderBodyOnly(true));
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
index bf28116..0c01695 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractLogsPanel.java
@@ -102,7 +102,7 @@ public abstract class AbstractLogsPanel<T extends Serializable> extends Panel {
 
         builder.setItems(loggerTOs).
                 setModel(new ListModel<>(loggerTOs)).
-                includes("key", "level").
+                includes(Constants.KEY_FIELD_NAME, "level").
                 withChecks(ListViewPanel.CheckAvailability.NONE).
                 setReuseItem(false);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java
index ab092a3..ad420c6 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSchemaDetailsPanel.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.client.console.panels;
 
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.common.lib.to.SchemaTO;
 import org.apache.wicket.markup.html.panel.Panel;
@@ -34,7 +35,10 @@ public abstract class AbstractSchemaDetailsPanel extends Panel {
     public AbstractSchemaDetailsPanel(final String id, final SchemaTO schemaTO) {
         super(id);
 
-        AjaxTextFieldPanel key = new AjaxTextFieldPanel("key", getString("key"), new PropertyModel<>(schemaTO, "key"));
+        AjaxTextFieldPanel key = new AjaxTextFieldPanel(
+                Constants.KEY_FIELD_NAME,
+                getString(Constants.KEY_FIELD_NAME),
+                new PropertyModel<>(schemaTO, Constants.KEY_FIELD_NAME));
         key.addRequiredLabel();
         key.setEnabled(schemaTO == null || schemaTO.getKey() == null || schemaTO.getKey().isEmpty());
         add(key);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
index 18e40d2..e185e69 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
@@ -22,13 +22,9 @@ import org.apache.syncope.client.ui.commons.panels.LabelPanel;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.Triple;
-import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo;
-import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
-import org.apache.syncope.client.console.layout.GroupFormLayoutInfo;
-import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.console.layout.AnyLayout;
+import org.apache.syncope.client.console.layout.AnyLayoutUtils;
 import org.apache.syncope.client.console.panels.search.AbstractSearchPanel;
 import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel;
 import org.apache.syncope.client.console.panels.search.GroupSearchPanel;
@@ -39,6 +35,7 @@ import org.apache.syncope.client.console.panels.search.UserSearchPanel;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
 import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.panels.ModalPanel;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
@@ -72,43 +69,63 @@ public class AnyPanel extends Panel implements ModalPanel {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AnyPanel.class);
 
-    private final AnyTypeTO anyTypeTO;
+    @FunctionalInterface
+    public interface DirectoryPanelSupplier {
 
-    private final RealmTO realmTO;
+        Panel supply(
+                String id,
+                AnyTypeTO anyTypeTO,
+                RealmTO realmTO,
+                AnyLayout anyLayout,
+                PageReference pageRef);
+    }
+
+    protected final AnyTypeTO anyTypeTO;
+
+    protected final RealmTO realmTO;
 
-    private final AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient();
+    protected final AnyLayout anyLayout;
 
-    private final Triple<UserFormLayoutInfo, GroupFormLayoutInfo, Map<String, AnyObjectFormLayoutInfo>> formLayoutInfo;
+    protected final PageReference pageRef;
 
-    private final PageReference pageRef;
+    protected AbstractSearchPanel searchPanel;
 
-    private AbstractSearchPanel searchPanel;
+    protected final Panel directoryPanel;
+
+    public AnyPanel(
+            final String id,
+            final AnyTypeTO anyTypeTO,
+            final RealmTO realmTO,
+            final AnyLayout anyLayout,
+            final boolean enableSearch,
+            final PageReference pageRef) {
 
-    private final Panel directoryPanel;
+        this(id, anyTypeTO, realmTO, anyLayout, enableSearch, DEFAULT_DIRECTORYPANEL_SUPPLIER, pageRef);
+    }
 
     public AnyPanel(
             final String id,
             final AnyTypeTO anyTypeTO,
             final RealmTO realmTO,
-            final Triple<UserFormLayoutInfo, GroupFormLayoutInfo, Map<String, AnyObjectFormLayoutInfo>> formLayoutInfo,
+            final AnyLayout anyLayout,
             final boolean enableSearch,
+            final DirectoryPanelSupplier directoryPanelSupplier,
             final PageReference pageRef) {
 
         super(id);
         this.anyTypeTO = anyTypeTO;
         this.realmTO = realmTO;
-        this.formLayoutInfo = formLayoutInfo;
+        this.anyLayout = anyLayout;
         this.pageRef = pageRef;
         // ------------------------
         // Accordion
         // ------------------------
         final Model<Integer> model = Model.of(-1);
-        final StringResourceModel res = new StringResourceModel("search.result", this, new Model<>(anyTypeTO));
-
+        final StringResourceModel searchResult = new StringResourceModel("search.result", this, new Model<>(anyTypeTO));
         final Accordion accordion = new Accordion("accordionPanel",
-                Collections.<ITab>singletonList(new AbstractTab(res) {
+                Collections.<ITab>singletonList(new AbstractTab(searchResult) {
 
-                    private static final long serialVersionUID = 1037272333056449377L;
+                    protected static final long serialVersionUID = 1037272333056449377L;
 
                     @Override
                     public WebMarkupContainer getPanel(final String panelId) {
@@ -118,13 +135,13 @@ public class AnyPanel extends Panel implements ModalPanel {
 
                 }), model) {
 
-            private static final long serialVersionUID = 1L;
+            protected static final long serialVersionUID = -3056452800492734900L;
 
             @Override
             protected Component newTitle(final String markupId, final ITab tab, final Accordion.State state) {
                 return new AjaxLink<Integer>(markupId) {
 
-                    private static final long serialVersionUID = 1L;
+                    protected static final long serialVersionUID = 6250423506463465679L;
 
                     @Override
                     protected void onComponentTag(final ComponentTag tag) {
@@ -136,13 +153,13 @@ public class AnyPanel extends Panel implements ModalPanel {
                     public void onClick(final AjaxRequestTarget target) {
                         model.setObject(model.getObject() == 0 ? -1 : 0);
                     }
-                }.setBody(res);
+                }.setBody(searchResult);
             }
         };
         accordion.setOutputMarkupId(true);
         add(accordion.setEnabled(enableSearch).setVisible(enableSearch));
 
-        directoryPanel = getDirectoryPanel("searchResult");
+        directoryPanel = directoryPanelSupplier.supply("searchResult", anyTypeTO, realmTO, anyLayout, pageRef);
         add(directoryPanel);
         // ------------------------
     }
@@ -150,15 +167,11 @@ public class AnyPanel extends Panel implements ModalPanel {
     @Override
     public void onEvent(final IEvent<?> event) {
         if (event.getPayload() instanceof SearchClausePanel.SearchEvent) {
-            final AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
+            AjaxRequestTarget target = SearchClausePanel.SearchEvent.class.cast(event.getPayload()).getTarget();
 
-            final String precond;
-
-            if (realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM)) {
-                precond = StringUtils.EMPTY;
-            } else {
-                precond = String.format("$dynRealms=~%s;", realmTO.getKey());
-            }
+            String precond = realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM)
+                    ? StringUtils.EMPTY
+                    : String.format("$dynRealms=~%s;", realmTO.getKey());
 
             switch (anyTypeTO.getKind()) {
                 case USER:
@@ -166,22 +179,31 @@ public class AnyPanel extends Panel implements ModalPanel {
                             precond + SearchUtils.buildFIQL(
                                     AnyPanel.this.searchPanel.getModel().getObject(),
                                     SyncopeClient.getUserSearchConditionBuilder(),
-                                    AnyPanel.this.searchPanel.getAvailableSchemaTypes()), target);
+                                    AnyPanel.this.searchPanel.getAvailableSchemaTypes(),
+                                    SearchUtils.NO_CUSTOM_CONDITION),
+                            target);
                     break;
+
                 case GROUP:
                     GroupDirectoryPanel.class.cast(AnyPanel.this.directoryPanel).search(
                             precond + SearchUtils.buildFIQL(
                                     AnyPanel.this.searchPanel.getModel().getObject(),
                                     SyncopeClient.getGroupSearchConditionBuilder(),
-                                    AnyPanel.this.searchPanel.getAvailableSchemaTypes()), target);
+                                    AnyPanel.this.searchPanel.getAvailableSchemaTypes(),
+                                    SearchUtils.NO_CUSTOM_CONDITION),
+                            target);
                     break;
+
                 case ANY_OBJECT:
                     AnyObjectDirectoryPanel.class.cast(AnyPanel.this.directoryPanel).search(
                             precond + SearchUtils.buildFIQL(
                                     AnyPanel.this.searchPanel.getModel().getObject(),
                                     SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()),
-                                    AnyPanel.this.searchPanel.getAvailableSchemaTypes()), target);
+                                    AnyPanel.this.searchPanel.getAvailableSchemaTypes(),
+                                    SearchUtils.NO_CUSTOM_CONDITION),
+                            target);
                     break;
+
                 default:
             }
         } else {
@@ -189,13 +211,12 @@ public class AnyPanel extends Panel implements ModalPanel {
         }
     }
 
-    private AbstractSearchPanel getSearchPanel(final String id) {
-        final AbstractSearchPanel panel;
-
-        final List<SearchClause> clauses = new ArrayList<>();
-        final SearchClause clause = new SearchClause();
+    protected AbstractSearchPanel getSearchPanel(final String id) {
+        List<SearchClause> clauses = new ArrayList<>();
+        SearchClause clause = new SearchClause();
         clauses.add(clause);
 
+        AbstractSearchPanel panel;
         switch (anyTypeTO.getKind()) {
             case USER:
                 clause.setComparator(SearchClause.Comparator.EQUALS);
@@ -205,6 +226,7 @@ public class AnyPanel extends Panel implements ModalPanel {
                 panel = new UserSearchPanel.Builder(
                         new ListModel<>(clauses)).required(true).enableSearch().build(id);
                 break;
+
             case GROUP:
                 clause.setComparator(SearchClause.Comparator.EQUALS);
                 clause.setType(SearchClause.Type.ATTRIBUTE);
@@ -213,6 +235,7 @@ public class AnyPanel extends Panel implements ModalPanel {
                 panel = new GroupSearchPanel.Builder(
                         new ListModel<>(clauses)).required(true).enableSearch().build(id);
                 break;
+
             case ANY_OBJECT:
                 clause.setComparator(SearchClause.Comparator.EQUALS);
                 clause.setType(SearchClause.Type.ATTRIBUTE);
@@ -221,92 +244,98 @@ public class AnyPanel extends Panel implements ModalPanel {
                 panel = new AnyObjectSearchPanel.Builder(anyTypeTO.getKey(),
                         new ListModel<>(clauses)).required(true).enableSearch().build(id);
                 break;
+
             default:
                 panel = null;
         }
         return panel;
     }
 
-    protected Panel getDirectoryPanel(final String id) {
-        final Panel panel;
-        String fiql;
-
-        final String realm;
-        final String dynRealm;
-        if (realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM)) {
-            realm = realmTO.getFullPath();
-            dynRealm = null;
-        } else {
-            realm = SyncopeConstants.ROOT_REALM;
-            dynRealm = realmTO.getKey();
-        }
-
-        switch (anyTypeTO.getKind()) {
-            case USER:
-                fiql = dynRealm == null
-                        ? SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue().query()
-                        : SyncopeClient.getUserSearchConditionBuilder().inDynRealms(dynRealm).query();
-
-                final UserTO userTO = new UserTO();
-                userTO.setRealm(realmTO.getFullPath());
-                panel = new UserDirectoryPanel.Builder(
-                        AnyTypeClassRestClient.list(anyTypeTO.getClasses()),
-                        anyTypeTO.getKey(),
-                        pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true).
-                        setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate(
-                        userTO,
-                        anyTypeTO.getClasses(),
-                        formLayoutInfo.getLeft(),
-                        pageRef)).build(id);
-                MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.RENDER, IdRepoEntitlement.USER_SEARCH);
-                break;
-
-            case GROUP:
-                fiql = dynRealm == null
-                        ? SyncopeClient.getGroupSearchConditionBuilder().is("key").notNullValue().query()
-                        : SyncopeClient.getGroupSearchConditionBuilder().inDynRealms(dynRealm).query();
-
-                final GroupTO groupTO = new GroupTO();
-                groupTO.setRealm(realmTO.getFullPath());
-                panel = new GroupDirectoryPanel.Builder(
-                        AnyTypeClassRestClient.list(anyTypeTO.getClasses()),
-                        anyTypeTO.getKey(),
-                        pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true).
-                        setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate(
-                        groupTO,
-                        anyTypeTO.getClasses(),
-                        formLayoutInfo.getMiddle(),
-                        pageRef)).build(id);
-                // list of group is available to all authenticated users
-                break;
-
-            case ANY_OBJECT:
-                fiql = dynRealm == null
-                        ? SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()).is("key").notNullValue()
-                                .query()
-                        : SyncopeClient.getAnyObjectSearchConditionBuilder(anyTypeTO.getKey()).inDynRealms(dynRealm)
-                                .query();
-
-                final AnyObjectTO anyObjectTO = new AnyObjectTO();
-                anyObjectTO.setRealm(realmTO.getFullPath());
-                anyObjectTO.setType(anyTypeTO.getKey());
-                panel = new AnyObjectDirectoryPanel.Builder(
-                        AnyTypeClassRestClient.list(anyTypeTO.getClasses()),
-                        anyTypeTO.getKey(),
-                        pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true).
-                        setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate(
-                        anyObjectTO,
-                        anyTypeTO.getClasses(),
-                        formLayoutInfo.getRight().get(anyTypeTO.getKey()),
-                        pageRef)).build(id);
-                MetaDataRoleAuthorizationStrategy.authorize(
-                        panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyTypeTO.getKey()));
-                break;
-
-            default:
-                panel = new LabelPanel(id, null);
-        }
-        return panel;
-    }
+    protected static DirectoryPanelSupplier DEFAULT_DIRECTORYPANEL_SUPPLIER =
+            (id, anyType, realmTO, anyLayout, pageRef) -> {
+
+                AnyTypeClassRestClient anyTypeClassRestClient = new AnyTypeClassRestClient();
+
+                final Panel panel;
+                String fiql;
+
+                final String realm;
+                final String dynRealm;
+                if (realmTO.getFullPath().startsWith(SyncopeConstants.ROOT_REALM)) {
+                    realm = realmTO.getFullPath();
+                    dynRealm = null;
+                } else {
+                    realm = SyncopeConstants.ROOT_REALM;
+                    dynRealm = realmTO.getKey();
+                }
+
+                switch (anyType.getKind()) {
+                    case USER:
+                        fiql = dynRealm == null
+                                ? SyncopeClient.getUserSearchConditionBuilder().
+                                        is(Constants.KEY_FIELD_NAME).notNullValue().query()
+                                : SyncopeClient.getUserSearchConditionBuilder().
+                                        inDynRealms(dynRealm).query();
+
+                        UserTO userTO = new UserTO();
+                        userTO.setRealm(realmTO.getFullPath());
+                        panel = new UserDirectoryPanel.Builder(
+                                anyTypeClassRestClient.list(anyType.getClasses()),
+                                anyType.getKey(),
+                                pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true).
+                                setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(
+                                AnyLayoutUtils.newLayoutInfo(
+                                        userTO, anyType.getClasses(), anyLayout.getUser(), pageRef)).
+                                build(id);
+                        MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.RENDER,
+                                IdRepoEntitlement.USER_SEARCH);
+                        break;
+
+                    case GROUP:
+                        fiql = dynRealm == null
+                                ? SyncopeClient.getGroupSearchConditionBuilder().
+                                        is(Constants.KEY_FIELD_NAME).notNullValue().query()
+                                : SyncopeClient.getGroupSearchConditionBuilder().inDynRealms(dynRealm).query();
+
+                        GroupTO groupTO = new GroupTO();
+                        groupTO.setRealm(realmTO.getFullPath());
+                        panel = new GroupDirectoryPanel.Builder(
+                                anyTypeClassRestClient.list(anyType.getClasses()),
+                                anyType.getKey(),
+                                pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true).
+                                setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(
+                                AnyLayoutUtils.newLayoutInfo(
+                                        groupTO, anyType.getClasses(), anyLayout.getGroup(), pageRef)).
+                                build(id);
+                        // list of group is available to all authenticated users
+                        break;
+
+                    case ANY_OBJECT:
+                        fiql = dynRealm == null
+                                ? SyncopeClient.getAnyObjectSearchConditionBuilder(anyType.getKey()).
+                                        is(Constants.KEY_FIELD_NAME).notNullValue().query()
+                                : SyncopeClient.getAnyObjectSearchConditionBuilder(anyType.getKey()).
+                                        inDynRealms(dynRealm).query();
+
+                        AnyObjectTO anyObjectTO = new AnyObjectTO();
+                        anyObjectTO.setRealm(realmTO.getFullPath());
+                        anyObjectTO.setType(anyType.getKey());
+                        panel = new AnyObjectDirectoryPanel.Builder(
+                                anyTypeClassRestClient.list(anyType.getClasses()),
+                                anyType.getKey(),
+                                pageRef).setRealm(realm).setDynRealm(dynRealm).setFiltered(true).
+                                setFiql(fiql).setWizardInModal(true).addNewItemPanelBuilder(
+                                AnyLayoutUtils.newLayoutInfo(anyObjectTO, anyType.getClasses(),
+                                        anyLayout.getAnyObjects().get(anyType.getKey()), pageRef)).
+                                build(id);
+                        MetaDataRoleAuthorizationStrategy.authorize(
+                                panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyType.getKey()));
+                        break;
+
+                    default:
+                        panel = new LabelPanel(id, null);
+                }
+                return panel;
+            };
 
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java
index f34ce87..5fc6999 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassDetailsPanel.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.panels;
 import java.util.List;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
 import org.apache.syncope.client.console.rest.SchemaRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
@@ -43,9 +44,7 @@ public class AnyTypeClassDetailsPanel extends Panel {
 
     private final List<String> availableVirSchemas = SchemaRestClient.getVirSchemaNames();
 
-    private static final List<String> LAYOUT_PARAMETERS =
-            List.of("admin.user.layout", "self.user.layout",
-        "admin.group.layout", "self.group.layout", "admin.membership.layout", "self.membership.layout");
+    private static final List<String> LAYOUT_PARAMETERS = List.of(Constants.ENDUSER_ANYLAYOUT);
 
     public AnyTypeClassDetailsPanel(final String id, final AnyTypeClassTO anyTypeClassTO) {
         super(id);
@@ -58,8 +57,10 @@ public class AnyTypeClassDetailsPanel extends Panel {
         antTypeClassForm.setOutputMarkupId(true);
         add(antTypeClassForm);
 
-        final AjaxTextFieldPanel key = new AjaxTextFieldPanel("key", getString("key"), new PropertyModel<>(
-                this.anyTypeClassTO, "key"));
+        final AjaxTextFieldPanel key = new AjaxTextFieldPanel(
+                Constants.KEY_FIELD_NAME,
+                getString(Constants.KEY_FIELD_NAME),
+                new PropertyModel<>(this.anyTypeClassTO, Constants.KEY_FIELD_NAME));
         key.addRequiredLabel();
         key.setEnabled(anyTypeClassTO.getKey() == null || this.anyTypeClassTO.getKey().isEmpty());
         antTypeClassForm.add(key);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
index cf4a25c..29f2937 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
@@ -140,7 +140,7 @@ public class AnyTypeClassesPanel extends TypesDirectoryPanel<
                         @Override
                         public String getCssClass() {
                             String css = super.getCssClass();
-                            if ("key".equals(fieldName)) {
+                            if (Constants.KEY_FIELD_NAME.equals(fieldName)) {
                                 css = StringUtils.isBlank(css)
                                         ? "col-xs-1"
                                         : css + " col-xs-1";
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java
index d562673..0b55628 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeDetailsPanel.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.panels;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
@@ -49,8 +50,10 @@ public class AnyTypeDetailsPanel extends Panel {
         form.setModel(new CompoundPropertyModel<>(anyTypeTO));
         container.add(form);
 
-        final AjaxTextFieldPanel key =
-                new AjaxTextFieldPanel("key", getString("key"), new PropertyModel<>(anyTypeTO, "key"));
+        final AjaxTextFieldPanel key = new AjaxTextFieldPanel(
+                Constants.KEY_FIELD_NAME,
+                getString(Constants.KEY_FIELD_NAME),
+                new PropertyModel<>(anyTypeTO, Constants.KEY_FIELD_NAME));
         key.addRequiredLabel();
         key.setEnabled(key.getModelObject() == null || key.getModelObject().isEmpty());
         form.add(key);
@@ -66,7 +69,7 @@ public class AnyTypeDetailsPanel extends Panel {
         form.add(kind);
 
         form.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build("classes",
-                new PropertyModel<List<String>>(anyTypeTO, "classes"),
+                new PropertyModel<>(anyTypeTO, "classes"),
                 new ListModel<>(getAvailableAnyTypeClasses())).hideLabel().setOutputMarkupId(true));
     }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
index e78e471..9cfd12f 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
@@ -139,7 +139,7 @@ public class AnyTypesPanel extends TypesDirectoryPanel<AnyTypeTO, AnyTypesPanel.
                         @Override
                         public String getCssClass() {
                             String css = super.getCssClass();
-                            if ("key".equals(fieldName)) {
+                            if (Constants.KEY_FIELD_NAME.equals(fieldName)) {
                                 css = StringUtils.isBlank(css)
                                         ? "col-xs-1"
                                         : css + " col-xs-1";
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java
index cd179a1..a4f1a28 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationDirectoryPanel.java
@@ -131,7 +131,8 @@ public class ApplicationDirectoryPanel extends
     protected List<IColumn<ApplicationTO, String>> getColumns() {
         final List<IColumn<ApplicationTO, String>> columns = new ArrayList<>();
 
-        columns.add(new PropertyColumn<>(new ResourceModel("key"), "key", "key"));
+        columns.add(new PropertyColumn<>(
+                new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(new ResourceModel("description"), "description", "description"));
         columns.add(new AbstractColumn<ApplicationTO, String>(new ResourceModel("privileges")) {
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java
index 4e831a9..9e4953c 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ApplicationModalPanel.java
@@ -51,7 +51,9 @@ public class ApplicationModalPanel extends AbstractModalPanel<ApplicationTO> {
         modal.setFormModel(application);
 
         AjaxTextFieldPanel key = new AjaxTextFieldPanel(
-                "key", "key", new PropertyModel<>(application, "key"), false);
+                Constants.KEY_FIELD_NAME,
+                Constants.KEY_FIELD_NAME,
+                new PropertyModel<>(application, Constants.KEY_FIELD_NAME), false);
         key.setReadOnly(!create);
         key.setRequired(true);
         add(key);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java
index 09cd23b..b38d0cc 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmDirectoryPanel.java
@@ -111,7 +111,8 @@ public class DynRealmDirectoryPanel extends
     protected List<IColumn<DynRealmTO, String>> getColumns() {
         final List<IColumn<DynRealmTO, String>> columns = new ArrayList<>();
 
-        columns.add(new PropertyColumn<>(new ResourceModel("key"), "key", "key"));
+        columns.add(new PropertyColumn<>(
+                new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
 
         return columns;
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java
index 6ca5ebd..b027087 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DynRealmModalPanel.java
@@ -66,7 +66,9 @@ public class DynRealmModalPanel extends AbstractModalPanel<DynRealmWrapper> {
         modal.setFormModel(dynRealmWrapper);
 
         AjaxTextFieldPanel key = new AjaxTextFieldPanel(
-                "key", "key", new PropertyModel<>(dynRealmWrapper.getInnerObject(), "key"), false);
+                Constants.KEY_FIELD_NAME,
+                Constants.KEY_FIELD_NAME,
+                new PropertyModel<>(dynRealmWrapper.getInnerObject(), Constants.KEY_FIELD_NAME), false);
         key.setReadOnly(!create);
         key.setRequired(true);
         add(key);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
index 645e628..869f3d9 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
@@ -24,12 +24,12 @@ import java.util.List;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.commons.IdRepoConstants;
-import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.audit.AuditHistoryModal;
-import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
+import org.apache.syncope.client.console.commons.IdRepoConstants;
+import org.apache.syncope.client.console.layout.AnyLayout;
+import org.apache.syncope.client.console.layout.AnyLayoutUtils;
 import org.apache.syncope.client.console.notifications.NotificationTasks;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
@@ -40,23 +40,23 @@ import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.Bas
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import org.apache.syncope.client.console.wizards.any.GroupWrapper;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.panels.ModalPanel;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
-import org.apache.syncope.common.lib.to.AnyTypeTO;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyEntitlement;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.ProvisionAction;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.common.lib.types.ProvisionAction;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
@@ -98,69 +98,70 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel<GroupTO, GroupRestCli
             protected Serializable onApplyInternal(
                     final GroupTO groupTO, final String type, final AjaxRequestTarget target) {
 
-                AnyTypeTO anyTypeTO = AnyTypeRestClient.read(type);
-
-                ModalPanel panel = new AnyPanel(BaseModal.CONTENT_ID, anyTypeTO, null, null, false, pageRef) {
-
-                    private static final long serialVersionUID = 7980820232811890502L;
-
-                    @Override
-                    protected Panel getDirectoryPanel(final String id) {
-
-                        final Panel panel;
-
-                        if (AnyTypeKind.USER.name().equals(type)) {
-                            String query = SyncopeClient.getUserSearchConditionBuilder().and(
-                                    SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()),
-                                    SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query();
-
-                            panel = new UserDirectoryPanel.Builder(
-                                    AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
-                                    setRealm(SyncopeConstants.ROOT_REALM).
-                                    setFiltered(true).
-                                    setFiql(query).
-                                    disableCheckBoxes().
-                                    addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate(
-                                            new UserTO(),
-                                            anyTypeTO.getClasses(),
-                                            FormLayoutInfoUtils.fetch(AnyTypeRestClient.list()).getLeft(),
-                                            pageRef), false).
-                                    setWizardInModal(false).build(id);
-
-                            MetaDataRoleAuthorizationStrategy.authorize(
-                                    panel, WebPage.RENDER, IdRepoEntitlement.USER_SEARCH);
-                        } else {
-                            String query = SyncopeClient.getAnyObjectSearchConditionBuilder(type).and(
-                                    SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()),
-                                    SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query();
-
-                            panel = new AnyObjectDirectoryPanel.Builder(
-                                    AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
-                                    setRealm(SyncopeConstants.ROOT_REALM).
-                                    setFiltered(true).
-                                    setFiql(query).
-                                    disableCheckBoxes().
-                                    addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate(
-                                            new AnyObjectTO(),
-                                            anyTypeTO.getClasses(),
-                                            FormLayoutInfoUtils.fetch(AnyTypeRestClient.list()).getRight().get(type),
-                                            pageRef), false).
-                                    setWizardInModal(false).build(id);
-
-                            MetaDataRoleAuthorizationStrategy.authorize(
-                                    panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyTypeTO.getKey()));
-                        }
-
-                        return panel;
-                    }
-                };
+                AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient();
+                AnyTypeClassRestClient classRestClient = new AnyTypeClassRestClient();
+
+                AnyLayout layout = AnyLayoutUtils.fetch(anyTypeRestClient.list());
+                ModalPanel anyPanel = AnyLayoutUtils.newAnyPanel(
+                        layout.getAnyPanelClass(),
+                        BaseModal.CONTENT_ID, anyTypeRestClient.read(type), null, layout, false,
+                        (id, anyTypeTO, realmTO, anyLayout, pageRef) -> {
+                            final Panel panel;
+                            if (AnyTypeKind.USER.name().equals(type)) {
+                                String query = SyncopeClient.getUserSearchConditionBuilder().and(
+                                        SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()),
+                                        SyncopeClient.getUserSearchConditionBuilder().
+                                                is(Constants.KEY_FIELD_NAME).notNullValue()).query();
+
+                                panel = new UserDirectoryPanel.Builder(
+                                        classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
+                                        setRealm(SyncopeConstants.ROOT_REALM).
+                                        setFiltered(true).
+                                        setFiql(query).
+                                        disableCheckBoxes().
+                                        addNewItemPanelBuilder(
+                                                AnyLayoutUtils.newLayoutInfo(
+                                                        new UserTO(),
+                                                        anyTypeTO.getClasses(),
+                                                        anyLayout.getUser(),
+                                                        pageRef), false).
+                                        setWizardInModal(false).build(id);
+
+                                MetaDataRoleAuthorizationStrategy.authorize(
+                                        panel, WebPage.RENDER, IdRepoEntitlement.USER_SEARCH);
+                            } else {
+                                String query = SyncopeClient.getAnyObjectSearchConditionBuilder(type).and(
+                                        SyncopeClient.getUserSearchConditionBuilder().inGroups(groupTO.getKey()),
+                                        SyncopeClient.getUserSearchConditionBuilder().
+                                                is(Constants.KEY_FIELD_NAME).notNullValue()).query();
+
+                                panel = new AnyObjectDirectoryPanel.Builder(
+                                        classRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
+                                        setRealm(SyncopeConstants.ROOT_REALM).
+                                        setFiltered(true).
+                                        setFiql(query).
+                                        disableCheckBoxes().
+                                        addNewItemPanelBuilder(AnyLayoutUtils.newLayoutInfo(
+                                                new AnyObjectTO(),
+                                                anyTypeTO.getClasses(),
+                                                layout.getAnyObjects().get(type),
+                                                pageRef), false).
+                                        setWizardInModal(false).build(id);
+
+                                MetaDataRoleAuthorizationStrategy.authorize(
+                                        panel, WebPage.RENDER, AnyEntitlement.SEARCH.getFor(anyTypeTO.getKey()));
+                            }
+
+                            return panel;
+                        },
+                        pageRef);
 
                 membersModal.header(new StringResourceModel(
                         "group.members",
                         GroupDirectoryPanel.this,
                         Model.of(Pair.of(groupTO, type))));
 
-                membersModal.setContent(panel);
+                membersModal.setContent(anyPanel);
                 membersModal.show(true);
                 target.add(membersModal);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java
index 8b76203..b5006c5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationDirectoryPanel.java
@@ -128,7 +128,9 @@ public class ImplementationDirectoryPanel extends DirectoryPanel<
     protected List<IColumn<ImplementationTO, String>> getColumns() {
         List<IColumn<ImplementationTO, String>> columns = new ArrayList<>();
 
-        columns.add(new PropertyColumn<>(new StringResourceModel("key", this), "key", "key"));
+        columns.add(new PropertyColumn<>(
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this),
+                Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(new StringResourceModel("engine", this), "engine", "engine"));
 
         return columns;
@@ -197,7 +199,7 @@ public class ImplementationDirectoryPanel extends DirectoryPanel<
         public ImplementationProvider(final int paginatorRows) {
             super(paginatorRows);
 
-            setSort("key", SortOrder.ASCENDING);
+            setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
             comparator = new SortableDataProviderComparator<>(this);
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java
index b7ca781..667af62 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ImplementationModalPanel.java
@@ -68,7 +68,9 @@ public class ImplementationModalPanel extends AbstractModalPanel<ImplementationT
         this.create = implementation.getKey() == null;
 
         add(new AjaxTextFieldPanel(
-                "key", "key", new PropertyModel<>(implementation, "key"), false).
+                Constants.KEY_FIELD_NAME,
+                Constants.KEY_FIELD_NAME,
+                new PropertyModel<>(implementation, Constants.KEY_FIELD_NAME), false).
                 addRequiredLabel().setEnabled(create));
 
         List<String> classes = SyncopeWebApplication.get().getImplementationInfoProvider().
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java
index 12422fe..6c19ef5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeDirectoryPanel.java
@@ -84,7 +84,8 @@ public class PrivilegeDirectoryPanel extends DirectoryPanel<
     protected List<IColumn<PrivilegeTO, String>> getColumns() {
         final List<IColumn<PrivilegeTO, String>> columns = new ArrayList<>();
 
-        columns.add(new PropertyColumn<>(new ResourceModel("key"), "key", "key"));
+        columns.add(new PropertyColumn<>(
+                new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(new ResourceModel("description"), "description", "description"));
 
         return columns;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java
index f2494fc..a3800e5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/PrivilegeWizardBuilder.java
@@ -20,6 +20,7 @@ package org.apache.syncope.client.console.panels;
 
 import java.io.Serializable;
 import org.apache.syncope.client.console.rest.ApplicationRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.client.console.wicket.markup.html.form.JsonEditorPanel;
 import org.apache.syncope.client.console.wizards.BaseAjaxWizardBuilder;
@@ -72,7 +73,8 @@ public class PrivilegeWizardBuilder extends BaseAjaxWizardBuilder<PrivilegeTO> {
                     : new StringResourceModel("privilege.edit", Model.of(privilege)));
 
             AjaxTextFieldPanel key = new AjaxTextFieldPanel(
-                    "key", "key", new PropertyModel<>(privilege, "key"), false);
+                    Constants.KEY_FIELD_NAME,
+                    Constants.KEY_FIELD_NAME, new PropertyModel<>(privilege, Constants.KEY_FIELD_NAME), false);
             key.setReadOnly(privilege.getKey() != null);
             key.setRequired(true);
             add(key);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
index 6735228..541bbe3 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
@@ -23,20 +23,16 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbed
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.commons.lang3.tuple.Triple;
 import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.commons.ConnIdSpecialName;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.console.commons.ITabComponent;
 import org.apache.syncope.client.ui.commons.status.StatusUtils;
-import org.apache.syncope.client.console.layout.AnyObjectFormLayoutInfo;
-import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
-import org.apache.syncope.client.console.layout.GroupFormLayoutInfo;
-import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.console.layout.AnyLayout;
+import org.apache.syncope.client.console.layout.AnyLayoutUtils;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
@@ -168,18 +164,17 @@ public abstract class Realm extends WizardMgtPanel<RealmTO> {
             }
         });
 
-        final Triple<UserFormLayoutInfo, GroupFormLayoutInfo, Map<String, AnyObjectFormLayoutInfo>> formLayoutInfo =
-                FormLayoutInfoUtils.fetch(anyTypes.stream().map(EntityTO::getKey).collect(Collectors.toList()));
-
-        for (final AnyTypeTO anyType : anyTypes) {
-            tabs.add(new ITabComponent(
-                    new Model<>(anyType.getKey()), String.format("%s_SEARCH", anyType.getKey())) {
+        AnyLayout anyLayout = AnyLayoutUtils.fetch(
+                anyTypes.stream().map(EntityTO::getKey).collect(Collectors.toList()));
+        for (AnyTypeTO anyType : anyTypes) {
+            tabs.add(new ITabComponent(new Model<>(anyType.getKey()), String.format("%s_SEARCH", anyType.getKey())) {
 
                 private static final long serialVersionUID = 1169585538404171118L;
 
                 @Override
                 public WebMarkupContainer getPanel(final String panelId) {
-                    return new AnyPanel(panelId, anyType, realmTO, formLayoutInfo, true, pageRef);
+                    return AnyLayoutUtils.newAnyPanel(
+                            anyLayout.getAnyPanelClass(), panelId, anyType, realmTO, anyLayout, true, pageRef);
                 }
 
                 @Override
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java
index c75ba2c..d878210 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypeDetailsPanel.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.client.console.panels;
 
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.common.lib.to.RelationshipTypeTO;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -43,8 +44,9 @@ public class RelationshipTypeDetailsPanel extends Panel {
         form.setModel(new CompoundPropertyModel<>(relationshipTypeTO));
         container.add(form);
 
-        final AjaxTextFieldPanel key = new AjaxTextFieldPanel("key", getString("key"),
-                new PropertyModel<>(relationshipTypeTO, "key"));
+        final AjaxTextFieldPanel key = new AjaxTextFieldPanel(
+                Constants.KEY_FIELD_NAME, getString(Constants.KEY_FIELD_NAME),
+                new PropertyModel<>(relationshipTypeTO, Constants.KEY_FIELD_NAME));
         key.addRequiredLabel();
         key.setEnabled(key.getModelObject() == null || key.getModelObject().isEmpty());
         form.add(key);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
index 4cb53b1..9a7b8b8 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
@@ -143,7 +143,7 @@ public class RelationshipTypesPanel extends TypesDirectoryPanel<
                         @Override
                         public String getCssClass() {
                             String css = super.getCssClass();
-                            if ("key".equals(fieldName)) {
+                            if (Constants.KEY_FIELD_NAME.equals(fieldName)) {
                                 css = StringUtils.isBlank(css)
                                         ? "col-xs-1"
                                         : css + " col-xs-1";
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java
index a1fadcc..077a11b 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/RoleDirectoryPanel.java
@@ -32,8 +32,9 @@ import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.commons.IdRepoConstants;
 import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
-import org.apache.syncope.client.console.layout.ConsoleLayoutInfo;
-import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
+import org.apache.syncope.client.console.layout.AnyLayout;
+import org.apache.syncope.client.console.layout.AnyLayoutWrapper;
+import org.apache.syncope.client.console.layout.AnyLayoutUtils;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.panels.RoleDirectoryPanel.RoleDataProvider;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
@@ -112,7 +113,7 @@ public class RoleDirectoryPanel extends DirectoryPanel<RoleTO, RoleWrapper, Role
         final List<IColumn<RoleTO, String>> columns = new ArrayList<>();
 
         columns.add(new PropertyColumn<>(
-                new ResourceModel("key"), "key", "key"));
+                new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(
                 new ResourceModel("entitlements", "Entitlements"), null, "entitlements"));
         columns.add(new PropertyColumn<>(
@@ -161,42 +162,43 @@ public class RoleDirectoryPanel extends DirectoryPanel<RoleTO, RoleWrapper, Role
 
             @Override
             public void onClick(final AjaxRequestTarget target, final RoleTO ignore) {
-                String query = SyncopeClient.getUserSearchConditionBuilder().and(
-                        SyncopeClient.getUserSearchConditionBuilder().inRoles(model.getObject().getKey()),
-                        SyncopeClient.getUserSearchConditionBuilder().is("key").notNullValue()).query();
-
-                AnyTypeTO anyTypeTO = AnyTypeRestClient.read(AnyTypeKind.USER.name());
-
-                ModalPanel panel = new AnyPanel(BaseModal.CONTENT_ID, anyTypeTO, null, null, false, pageRef) {
-
-                    private static final long serialVersionUID = -7514498203393023415L;
-
-                    @Override
-                    protected Panel getDirectoryPanel(final String id) {
-                        Panel panel = new UserDirectoryPanel.Builder(
-                            AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
-                                setRealm(SyncopeConstants.ROOT_REALM).
-                                setFiltered(true).
-                                setFiql(query).
-                                disableCheckBoxes().
-                                addNewItemPanelBuilder(FormLayoutInfoUtils.instantiate(
-                                        new UserTO(),
-                                        anyTypeTO.getClasses(),
-                                        FormLayoutInfoUtils.fetch(AnyTypeRestClient.list()).getLeft(),
-                                        pageRef), false).
-                                setWizardInModal(false).build(id);
-
-                        MetaDataRoleAuthorizationStrategy.authorize(
-                                panel,
-                                WebPage.RENDER,
-                                IdRepoEntitlement.USER_SEARCH);
-
-                        return panel;
-                    }
-                };
+                AnyTypeTO userType = AnyTypeRestClient.read(AnyTypeKind.USER.name());
+
+                AnyLayout layout = AnyLayoutUtils.fetch(AnyTypeRestClient.list());
+                ModalPanel anyPanel = AnyLayoutUtils.newAnyPanel(
+                        layout.getAnyPanelClass(),
+                        BaseModal.CONTENT_ID, userType, null, layout, false,
+                        (id, anyTypeTO, realmTO, anyLayout, pageRef) -> {
+                            String query = SyncopeClient.getUserSearchConditionBuilder().and(
+                                    SyncopeClient.getUserSearchConditionBuilder().inRoles(model.getObject().getKey()),
+                                    SyncopeClient.getUserSearchConditionBuilder().
+                                            is(Constants.KEY_FIELD_NAME).notNullValue()).
+                                    query();
+
+                            Panel panel = new UserDirectoryPanel.Builder(
+                                    AnyTypeClassRestClient.list(anyTypeTO.getClasses()), anyTypeTO.getKey(), pageRef).
+                                    setRealm(SyncopeConstants.ROOT_REALM).
+                                    setFiltered(true).
+                                    setFiql(query).
+                                    disableCheckBoxes().
+                                    addNewItemPanelBuilder(AnyLayoutUtils.newLayoutInfo(
+                                            new UserTO(),
+                                            anyTypeTO.getClasses(),
+                                            anyLayout.getUser(),
+                                            pageRef), false).
+                                    setWizardInModal(false).build(id);
+
+                            MetaDataRoleAuthorizationStrategy.authorize(
+                                    panel,
+                                    WebPage.RENDER,
+                                    IdRepoEntitlement.USER_SEARCH);
+
+                            return panel;
+                        },
+                        pageRef);
 
                 membersModal.header(new StringResourceModel("role.members", RoleDirectoryPanel.this, model));
-                membersModal.setContent(panel);
+                membersModal.setContent(anyPanel);
                 membersModal.show(true);
                 target.add(membersModal);
             }
@@ -208,24 +210,26 @@ public class RoleDirectoryPanel extends DirectoryPanel<RoleTO, RoleWrapper, Role
 
             @Override
             public void onClick(final AjaxRequestTarget target, final RoleTO ignore) {
-                ConsoleLayoutInfo info = new ConsoleLayoutInfo(model.getObject().getKey());
-                info.setContent(RoleRestClient.readConsoleLayoutInfo(model.getObject().getKey()));
+                AnyLayoutWrapper wrapper = new AnyLayoutWrapper(
+                        model.getObject().getKey(),
+                        AnyLayoutUtils.defaultIfEmpty(
+                                RoleRestClient.readAnyLayout(model.getObject().getKey()), AnyTypeRestClient.list()));
 
                 utilityModal.header(new ResourceModel("console.layout.info", "JSON Content"));
                 utilityModal.setContent(new JsonEditorPanel(
-                        utilityModal, new PropertyModel<String>(info, "content"), false, pageRef) {
+                        utilityModal, new PropertyModel<String>(wrapper, "content"), false, pageRef) {
 
                     private static final long serialVersionUID = -8927036362466990179L;
 
                     @Override
                     public void onSubmit(final AjaxRequestTarget target) {
                         try {
-                            RoleRestClient.setConsoleLayoutInfo(info.getKey(), info.getContent());
+                            RoleRestClient.setAnyLayout(wrapper.getKey(), wrapper.getContent());
                             SyncopeConsoleSession.get().info(getString(Constants.OPERATION_SUCCEEDED));
                             modal.show(false);
                             modal.close(target);
                         } catch (Exception e) {
-                            LOG.error("While updating console layout info for role {}", info.getKey(), e);
+                            LOG.error("While updating console layout for role {}", wrapper.getKey(), e);
                             SyncopeConsoleSession.get().error(StringUtils.isBlank(e.getMessage())
                                     ? e.getClass().getName() : e.getMessage());
                         }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
index 2c36a72..f67d3ba 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
@@ -22,7 +22,6 @@ import java.io.Serializable;
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -60,17 +59,14 @@ public class SchemaTypePanel extends TypesDirectoryPanel<SchemaTO, SchemaProvide
 
     private static final long serialVersionUID = 3905038169553185171L;
 
-    private static final Map<SchemaType, List<String>> COL_NAMES = new HashMap<SchemaType, List<String>>() {
-
-        private static final long serialVersionUID = 3109256773218160485L;
-
-        {
-            put(SchemaType.PLAIN,
-                List.of("key", "type", "mandatoryCondition", "uniqueConstraint", "multivalue", "readonly"));
-            put(SchemaType.DERIVED, List.of("key", "expression"));
-            put(SchemaType.VIRTUAL, List.of("key", "resource", "anyType", "extAttrName", "readonly"));
-        }
-    };
+    private static final Map<SchemaType, List<String>> COL_NAMES = Map.of(
+            SchemaType.PLAIN,
+            List.of(Constants.KEY_FIELD_NAME,
+                    "type", "mandatoryCondition", "uniqueConstraint", "multivalue", "readonly"),
+            SchemaType.DERIVED,
+            List.of(Constants.KEY_FIELD_NAME, "expression"),
+            SchemaType.VIRTUAL,
+            List.of(Constants.KEY_FIELD_NAME, "resource", "anyType", "extAttrName", "readonly"));
 
     private final SchemaType schemaType;
 
@@ -137,7 +133,7 @@ public class SchemaTypePanel extends TypesDirectoryPanel<SchemaTO, SchemaProvide
                         @Override
                         public String getCssClass() {
                             String css = super.getCssClass();
-                            if ("key".equals(field)) {
+                            if (Constants.KEY_FIELD_NAME.equals(field)) {
                                 css = StringUtils.isBlank(css)
                                         ? "col-xs-1"
                                         : css + " col-xs-1";
@@ -213,7 +209,7 @@ public class SchemaTypePanel extends TypesDirectoryPanel<SchemaTO, SchemaProvide
             super(paginatorRows);
 
             this.schemaType = schemaType;
-            setSort("key", SortOrder.ASCENDING);
+            setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
             comparator = new SortableDataProviderComparator<>(this);
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
index 75b2227..dd5a091 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
@@ -129,7 +129,7 @@ public class SecurityQuestionsPanel extends DirectoryPanel<
         List<IColumn<SecurityQuestionTO, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(
                 new StringResourceModel("content", this), "content", "content"));
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java
index 547d8a3..2d80d22 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java
@@ -84,6 +84,9 @@ public abstract class AbstractSearchPanel extends Panel {
 
         protected boolean enableSearch = false;
 
+        protected SearchClausePanel.Customizer customizer = new SearchClausePanel.Customizer() {
+        };
+
         protected IEventSink resultContainer;
 
         public Builder(final IModel<List<SearchClause>> model) {
@@ -100,6 +103,11 @@ public abstract class AbstractSearchPanel extends Panel {
             return this;
         }
 
+        public Builder<T> customizer(final SearchClausePanel.Customizer customizer) {
+            this.customizer = customizer;
+            return this;
+        }
+
         public Builder<T> required(final boolean required) {
             this.required = required;
             return this;
@@ -134,10 +142,12 @@ public abstract class AbstractSearchPanel extends Panel {
         searchFormContainer.setOutputMarkupId(true);
         add(searchFormContainer);
 
-        final SearchClausePanel searchClausePanel = new SearchClausePanel("panel", "panel",
+        SearchClausePanel searchClausePanel = new SearchClausePanel("panel", "panel",
                 Model.of(new SearchClause()),
                 required,
-                types, anames, dnames, groupInfo, roleNames, privilegeNames, resourceNames);
+                types,
+                builder.customizer,
+                anames, dnames, groupInfo, roleNames, privilegeNames, resourceNames);
 
         if (enableSearch) {
             searchClausePanel.enableSearch(builder.resultContainer);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
index 77f2029..23b5363 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
@@ -32,7 +32,7 @@ import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 
-public final class GroupSearchPanel extends AbstractSearchPanel {
+public class GroupSearchPanel extends AbstractSearchPanel {
 
     private static final long serialVersionUID = 5757183539269316263L;
 
@@ -50,7 +50,7 @@ public final class GroupSearchPanel extends AbstractSearchPanel {
         }
     }
 
-    private GroupSearchPanel(final String id, final GroupSearchPanel.Builder builder) {
+    protected GroupSearchPanel(final String id, final GroupSearchPanel.Builder builder) {
         super(id, AnyTypeKind.GROUP, builder);
     }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java
index 2953787..69f2d15 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java
@@ -42,7 +42,8 @@ public final class SearchClause implements Serializable {
         ROLE_MEMBERSHIP,
         PRIVILEGE,
         RESOURCE,
-        RELATIONSHIP;
+        RELATIONSHIP,
+        CUSTOM;
 
     }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
index b1dae46..db6441d 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
@@ -22,9 +22,9 @@ import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.boot
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig;
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
@@ -57,6 +57,7 @@ import org.apache.wicket.event.IEventSink;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.form.CheckBox;
+import org.apache.wicket.markup.html.form.ChoiceRenderer;
 import org.apache.wicket.markup.html.form.FormComponent;
 import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.markup.html.list.ListItem;
@@ -70,10 +71,49 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
 
     private static final long serialVersionUID = -527351923968737757L;
 
+    public interface Customizer extends Serializable {
+
+        default IChoiceRenderer<SearchClause.Type> typeRenderer() {
+            return new ChoiceRenderer<>();
+        }
+
+        default List<Comparator> comparators() {
+            return List.of();
+        }
+
+        default String comparatorDisplayValue(Comparator object) {
+            return object.toString();
+        }
+
+        default Optional<SearchClause.Comparator> comparatorGetObject(String id) {
+            return Optional.empty();
+        }
+
+        default List<String> properties() {
+            return List.of();
+        }
+
+        default void setFieldAccess(
+                AjaxTextFieldPanel value,
+                AjaxTextFieldPanel property,
+                LoadableDetachableModel<List<String>> properties) {
+
+            value.setEnabled(true);
+            value.setModelObject(StringUtils.EMPTY);
+            property.setEnabled(true);
+
+            // reload properties list
+            properties.detach();
+            property.setChoices(properties.getObject());
+        }
+    }
+
     private final boolean required;
 
     private final IModel<List<SearchClause.Type>> types;
 
+    private final Customizer customizer;
+
     private final IModel<Map<String, PlainSchemaTO>> anames;
 
     private final IModel<List<String>> dnames;
@@ -108,6 +148,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
             final Model<SearchClause> clause,
             final boolean required,
             final IModel<List<SearchClause.Type>> types,
+            final Customizer customizer,
             final IModel<Map<String, PlainSchemaTO>> anames,
             final IModel<List<String>> dnames,
             final Pair<IModel<Map<String, String>>, Integer> groupInfo,
@@ -121,6 +162,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
 
         this.required = required;
         this.types = types;
+        this.customizer = customizer;
         this.anames = anames;
         this.dnames = dnames;
         this.groupInfo = groupInfo;
@@ -162,7 +204,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
             @Override
             protected List<Comparator> load() {
                 if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
-                    return Collections.<Comparator>emptyList();
+                    return List.of();
                 }
 
                 switch (field.getModel().getObject().getType()) {
@@ -184,6 +226,10 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 SearchClause.Comparator.IS_NULL,
                                 SearchClause.Comparator.EQUALS,
                                 SearchClause.Comparator.NOT_EQUALS);
+
+                    case CUSTOM:
+                        return customizer.comparators();
+
                     default:
                         return List.of();
                 }
@@ -202,37 +248,34 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
 
                 switch (field.getModel().getObject().getType()) {
                     case ATTRIBUTE:
-                        final List<String> names = new ArrayList<>(dnames.getObject());
+                        List<String> names = new ArrayList<>(dnames.getObject());
                         if (anames != null && anames.getObject() != null && !anames.getObject().isEmpty()) {
                             names.addAll(anames.getObject().keySet());
                         }
                         return names.stream().sorted().collect(Collectors.toList());
 
                     case GROUP_MEMBERSHIP:
-                        final List<String> groups = groupInfo.getLeft().getObject().values().
-                                stream().collect(Collectors.toList());
-                        Collections.sort(groups);
-                        return groups;
+                        return groupInfo.getLeft().getObject().values().stream().
+                                sorted().collect(Collectors.toList());
 
                     case ROLE_MEMBERSHIP:
-                        final List<String> roles = new ArrayList<>(roleNames.getObject());
-                        Collections.sort(roles);
-                        return roles;
+                        return roleNames.getObject().stream().
+                                sorted().collect(Collectors.toList());
 
                     case PRIVILEGE:
-                        final List<String> privileges = new ArrayList<>(privilegeNames.getObject());
-                        Collections.sort(privileges);
-                        return privileges;
+                        return privilegeNames.getObject().stream().
+                                sorted().collect(Collectors.toList());
 
                     case RESOURCE:
-                        final List<String> resources = new ArrayList<>(resourceNames.getObject());
-                        Collections.sort(resources);
-                        return resources;
+                        return resourceNames.getObject().stream().
+                                sorted().collect(Collectors.toList());
 
                     case RELATIONSHIP:
-                        final List<String> relations = RelationshipTypeRestClient.list().stream().
+                        return RelationshipTypeRestClient.list().stream().
                                 map(RelationshipTypeTO::getKey).collect(Collectors.toList());
-                        return relations;
+
+                    case CUSTOM:
+                        return customizer.properties();
 
                     default:
                         return List.of();
@@ -487,9 +530,10 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
             }
         });
 
-        final AjaxDropDownChoicePanel<SearchClause.Type> type = new AjaxDropDownChoicePanel<>(
+        AjaxDropDownChoicePanel<SearchClause.Type> type = new AjaxDropDownChoicePanel<>(
                 "type", "type", new PropertyModel<>(searchClause, "type"));
-        type.setChoices(types).hideLabel().setRequired(required).setOutputMarkupId(true);
+        type.setChoices(types).setChoiceRenderer(customizer.typeRenderer()).
+                hideLabel().setRequired(required).setOutputMarkupId(true);
         type.setNullValid(false);
         type.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
 
@@ -523,8 +567,10 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
             protected void onUpdate(final AjaxRequestTarget target) {
                 if (type.getModelObject() == SearchClause.Type.ATTRIBUTE
                         || type.getModelObject() == SearchClause.Type.RELATIONSHIP) {
+
                     if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
                             || comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
+
                         value.setModelObject(StringUtils.EMPTY);
                         value.setEnabled(false);
                     } else {
@@ -536,7 +582,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                 if (type.getModelObject() == SearchClause.Type.RELATIONSHIP) {
                     property.setEnabled(true);
 
-                    final SearchClause searchClause = new SearchClause();
+                    SearchClause searchClause = new SearchClause();
                     searchClause.setType(Type.valueOf(type.getDefaultModelObjectAsString()));
                     searchClause.setComparator(comparator.getModelObject());
                     SearchClausePanel.this.clause.setObject(searchClause);
@@ -555,7 +601,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
             final Type type,
             final AjaxTextFieldPanel property,
             final FieldPanel<Comparator> comparator,
-            final FieldPanel<String> value) {
+            final AjaxTextFieldPanel value) {
 
         if (type != null) {
             property.setEnabled(true);
@@ -632,20 +678,23 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                     property.setChoices(properties.getObject());
                     break;
 
+                case CUSTOM:
+                    customizer.setFieldAccess(value, property, properties);
+                    break;
+
                 default:
                     break;
             }
         }
     }
 
-    private static IChoiceRenderer<SearchClause.Comparator> getComparatorRender(final IModel<SearchClause> clause) {
+    private IChoiceRenderer<SearchClause.Comparator> getComparatorRender(final IModel<SearchClause> clause) {
         return new IChoiceRenderer<SearchClause.Comparator>() {
 
             private static final long serialVersionUID = -9086043750227867686L;
 
             @Override
             public Object getDisplayValue(final SearchClause.Comparator object) {
-
                 if (clause == null || clause.getObject() == null || clause.getObject().getType() == null) {
                     return object.toString();
                 }
@@ -691,6 +740,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 display = StringUtils.EMPTY;
                         }
                         break;
+
                     case GROUP_MEMBERSHIP:
                         switch (object) {
                             case EQUALS:
@@ -705,6 +755,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 display = StringUtils.EMPTY;
                         }
                         break;
+
                     case GROUP_MEMBER:
                         switch (object) {
                             case EQUALS:
@@ -719,6 +770,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 display = StringUtils.EMPTY;
                         }
                         break;
+
                     case ROLE_MEMBERSHIP:
                     case PRIVILEGE:
                     case RESOURCE:
@@ -735,6 +787,7 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 display = StringUtils.EMPTY;
                         }
                         break;
+
                     case RELATIONSHIP:
                         switch (object) {
                             case IS_NOT_NULL:
@@ -757,6 +810,11 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                                 display = StringUtils.EMPTY;
                         }
                         break;
+
+                    case CUSTOM:
+                        display = customizer.comparatorDisplayValue(object);
+                        break;
+
                     default:
                         display = object.toString();
                 }
@@ -776,58 +834,69 @@ public class SearchClausePanel extends FieldPanel<SearchClause> {
                     return SearchClause.Comparator.EQUALS;
                 }
 
-                final SearchClause.Comparator res;
+                final SearchClause.Comparator comparator;
                 switch (id) {
                     case "HAS":
                     case "IN":
                     case "WITH":
-                        res = SearchClause.Comparator.EQUALS;
+                        comparator = SearchClause.Comparator.EQUALS;
                         break;
+
                     case "HAS NOT":
                     case "NOT IN":
                     case "WITHOUT":
-                        res = SearchClause.Comparator.NOT_EQUALS;
+                        comparator = SearchClause.Comparator.NOT_EQUALS;
                         break;
+
                     case "NULL":
                     case "NOT EXIST":
-                        res = SearchClause.Comparator.IS_NULL;
+                        comparator = SearchClause.Comparator.IS_NULL;
                         break;
+
                     case "NOT NULL":
                     case "EXIST":
-                        res = SearchClause.Comparator.IS_NOT_NULL;
+                        comparator = SearchClause.Comparator.IS_NOT_NULL;
                         break;
+
                     case "==":
-                        res = SearchClause.Comparator.EQUALS;
+                        comparator = SearchClause.Comparator.EQUALS;
                         break;
+
                     case "!=":
-                        res = SearchClause.Comparator.NOT_EQUALS;
+                        comparator = SearchClause.Comparator.NOT_EQUALS;
                         break;
+
                     case "<":
-                        res = SearchClause.Comparator.LESS_THAN;
+                        comparator = SearchClause.Comparator.LESS_THAN;
                         break;
+
                     case "<=":
-                        res = SearchClause.Comparator.LESS_OR_EQUALS;
+                        comparator = SearchClause.Comparator.LESS_OR_EQUALS;
                         break;
+
                     case ">":
-                        res = SearchClause.Comparator.GREATER_THAN;
+                        comparator = SearchClause.Comparator.GREATER_THAN;
                         break;
+
                     case ">=":
-                        res = SearchClause.Comparator.GREATER_OR_EQUALS;
+                        comparator = SearchClause.Comparator.GREATER_OR_EQUALS;
                         break;
+
                     default:
                         // EQUALS to be used as default value
-                        res = SearchClause.Comparator.EQUALS;
+                        comparator = customizer.comparatorGetObject(id).orElse(SearchClause.Comparator.EQUALS);
                         break;
                 }
-                return res;
+
+                return comparator;
             }
         };
     }
 
     @Override
     public FieldPanel<SearchClause> clone() {
-        final SearchClausePanel panel = new SearchClausePanel(
-                getId(), name, null, required, types, anames, dnames, groupInfo,
+        SearchClausePanel panel = new SearchClausePanel(
+                getId(), name, null, required, types, customizer, anames, dnames, groupInfo,
                 roleNames, privilegeNames, resourceNames);
         panel.setReadOnly(this.isReadOnly());
         panel.setRequired(this.isRequired());
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
index d858130..4b9c991 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
@@ -20,12 +20,12 @@ package org.apache.syncope.client.console.panels.search;
 
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Function;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.math.NumberUtils;
@@ -51,17 +51,16 @@ public final class SearchUtils implements Serializable {
 
     private static final Logger LOG = LoggerFactory.getLogger(SearchUtils.class);
 
+    public static final Function<SearchClause, CompleteCondition> NO_CUSTOM_CONDITION = clause -> null;
+
     private static Pattern getTypeConditionPattern(final String type) {
         return Pattern.compile(String.format(";\\$type==%s|\\$type==%s;", type, type));
     }
 
     public static Map<String, List<SearchClause>> getSearchClauses(final Map<String, String> fiql) {
-        Map<String, List<SearchClause>> clauses = new HashMap<>();
-        for (Map.Entry<String, String> entry : fiql.entrySet()) {
-            clauses.put(entry.getKey(), getSearchClauses(
-                    entry.getValue().replaceAll(getTypeConditionPattern(entry.getKey()).pattern(), "")));
-        }
-        return clauses;
+        return fiql.entrySet().stream().collect(Collectors.toMap(
+                Map.Entry::getKey,
+                e -> getSearchClauses(e.getValue().replaceAll(getTypeConditionPattern(e.getKey()).pattern(), ""))));
     }
 
     public static List<SearchClause> getSearchClauses(final String fiql) {
@@ -93,7 +92,7 @@ public final class SearchUtils implements Serializable {
     private static List<SearchClause> getCompoundSearchClauses(final SearchCondition<SearchBean> sc) {
         List<SearchClause> clauses = new ArrayList<>();
 
-        for (SearchCondition<SearchBean> searchCondition : sc.getSearchConditions()) {
+        sc.getSearchConditions().forEach(searchCondition -> {
             if (searchCondition.getStatement() == null) {
                 clauses.addAll(getCompoundSearchClauses(searchCondition));
             } else {
@@ -106,7 +105,7 @@ public final class SearchUtils implements Serializable {
                 }
                 clauses.add(clause);
             }
-        }
+        });
 
         return clauses;
     }
@@ -142,6 +141,9 @@ public final class SearchUtils implements Serializable {
         } else if (SpecialAttr.MEMBER.toString().equals(property)) {
             clause.setType(SearchClause.Type.GROUP_MEMBER);
             clause.setProperty(value);
+        } else if (property.startsWith("$")) {
+            clause.setType(SearchClause.Type.CUSTOM);
+            clause.setProperty(value);
         } else {
             clause.setType(SearchClause.Type.ATTRIBUTE);
         }
@@ -200,14 +202,16 @@ public final class SearchUtils implements Serializable {
     }
 
     public static String buildFIQL(final List<SearchClause> clauses, final AbstractFiqlSearchConditionBuilder builder) {
-        return buildFIQL(clauses, builder, Collections.<String, PlainSchemaTO>emptyMap());
+        return buildFIQL(clauses, builder, Map.of(), NO_CUSTOM_CONDITION);
     }
 
     public static String buildFIQL(
             final List<SearchClause> clauses,
             final AbstractFiqlSearchConditionBuilder builder,
-            final Map<String, PlainSchemaTO> availableSchemaTypes) {
-        LOG.debug("Generating FIQL from List<SearchClause>: {}", clauses);
+            final Map<String, PlainSchemaTO> availableSchemaTypes,
+            final Function<SearchClause, CompleteCondition> customCondition) {
+
+        LOG.debug("Generating FIQL from {}", clauses);
 
         CompleteCondition prevCondition;
         CompleteCondition condition = null;
@@ -224,16 +228,18 @@ public final class SearchUtils implements Serializable {
 
                 switch (clause.getType()) {
                     case GROUP_MEMBER:
-                        switch (clause.getComparator()) {
-                            case EQUALS:
-                                condition = ((GroupFiqlSearchConditionBuilder) builder).withMembers(value);
-                                break;
+                        if (builder instanceof GroupFiqlSearchConditionBuilder) {
+                            switch (clause.getComparator()) {
+                                case EQUALS:
+                                    condition = ((GroupFiqlSearchConditionBuilder) builder).withMembers(value);
+                                    break;
 
-                            case NOT_EQUALS:
-                                condition = ((GroupFiqlSearchConditionBuilder) builder).withoutMembers(value);
-                                break;
+                                case NOT_EQUALS:
+                                    condition = ((GroupFiqlSearchConditionBuilder) builder).withoutMembers(value);
+                                    break;
 
-                            default:
+                                default:
+                            }
                         }
                         break;
 
@@ -356,7 +362,9 @@ public final class SearchUtils implements Serializable {
                         break;
 
                     case ROLE_MEMBERSHIP:
-                        if (StringUtils.isNotBlank(clause.getProperty())) {
+                        if (StringUtils.isNotBlank(clause.getProperty())
+                                && builder instanceof UserFiqlSearchConditionBuilder) {
+
                             switch (clause.getComparator()) {
                                 case EQUALS:
                                     condition = ((UserFiqlSearchConditionBuilder) builder).
@@ -373,7 +381,9 @@ public final class SearchUtils implements Serializable {
                         break;
 
                     case PRIVILEGE:
-                        if (StringUtils.isNotBlank(clause.getProperty())) {
+                        if (StringUtils.isNotBlank(clause.getProperty())
+                                && builder instanceof UserFiqlSearchConditionBuilder) {
+
                             switch (clause.getComparator()) {
                                 case EQUALS:
                                     condition = ((UserFiqlSearchConditionBuilder) builder).
@@ -437,6 +447,10 @@ public final class SearchUtils implements Serializable {
                         }
                         break;
 
+                    case CUSTOM:
+                        condition = customCondition.apply(clause);
+                        break;
+
                     default:
                         break;
                 }
@@ -463,5 +477,4 @@ public final class SearchUtils implements Serializable {
     private SearchUtils() {
         // private constructor for static utility class
     }
-
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java
index e2e3e56..1f8f982 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java
@@ -28,7 +28,7 @@ import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 
-public final class UserSearchPanel extends AnyObjectSearchPanel {
+public class UserSearchPanel extends AnyObjectSearchPanel {
 
     private static final long serialVersionUID = -1769527800450203738L;
 
@@ -46,7 +46,7 @@ public final class UserSearchPanel extends AnyObjectSearchPanel {
         }
     }
 
-    private UserSearchPanel(final String id, final Builder builder) {
+    protected UserSearchPanel(final String id, final Builder builder) {
         super(id, AnyTypeKind.USER, builder);
     }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java
index 84736c0..e4c6fb3 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/policies/PolicyDirectoryPanel.java
@@ -119,7 +119,7 @@ public abstract class PolicyDirectoryPanel<T extends PolicyTO>
         final List<IColumn<T, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(
                 new StringResourceModel("description", this), "description", "description"));
         columns.add(new CollectionPropertyColumn<>(
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
index 9cf57fb..c8b09cf 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportDirectoryPanel.java
@@ -106,7 +106,8 @@ public abstract class ReportDirectoryPanel
     protected List<IColumn<ReportTO, String>> getColumns() {
         final List<IColumn<ReportTO, String>> columns = new ArrayList<>();
 
-        columns.add(new KeyPropertyColumn<>(new StringResourceModel("key", this), "key"));
+        columns.add(new KeyPropertyColumn<>(
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
         columns.add(new PropertyColumn<>(new StringResourceModel("name", this), "name", "name"));
 
         columns.add(new DatePropertyColumn<>(
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportTemplateDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportTemplateDirectoryPanel.java
index c6d714d..53f2a81 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportTemplateDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/reports/ReportTemplateDirectoryPanel.java
@@ -110,7 +110,9 @@ public class ReportTemplateDirectoryPanel
     @Override
     protected List<IColumn<ReportTemplateTO, String>> getColumns() {
         List<IColumn<ReportTemplateTO, String>> columns = new ArrayList<>();
-        columns.add(new PropertyColumn<>(new StringResourceModel("key", this), "key", "key"));
+        columns.add(new PropertyColumn<>(
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this),
+                Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
         return columns;
     }
 
@@ -218,7 +220,7 @@ public class ReportTemplateDirectoryPanel
 
         public ReportTemplateProvider(final int paginatorRows) {
             super(paginatorRows);
-            setSort("key", SortOrder.ASCENDING);
+            setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
             comparator = new SortableDataProviderComparator<>(this);
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java
index 93f9da4..5c53661 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/RoleRestClient.java
@@ -20,9 +20,8 @@ package org.apache.syncope.client.console.rest;
 
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.to.RoleTO;
@@ -55,10 +54,10 @@ public class RoleRestClient extends BaseRestClient {
         return getService(RoleService.class).list();
     }
 
-    public static String readConsoleLayoutInfo(final String roleKey) {
+    public static String readAnyLayout(final String roleKey) {
         try {
             return IOUtils.toString(InputStream.class.cast(
-                    getService(RoleService.class).getConsoleLayoutInfo(roleKey).getEntity()),
+                    getService(RoleService.class).getAnyLayout(roleKey).getEntity()),
                     StandardCharsets.UTF_8);
         } catch (Exception e) {
             LOG.error("Error retrieving console layout info for role {}", roleKey, e);
@@ -66,18 +65,16 @@ public class RoleRestClient extends BaseRestClient {
         }
     }
 
-    public static void setConsoleLayoutInfo(final String roleKey, final String content) {
-        getService(RoleService.class).setConsoleLayoutInfo(
+    public static void setAnyLayout(final String roleKey, final String content) {
+        getService(RoleService.class).setAnyLayout(
                 roleKey, IOUtils.toInputStream(content, StandardCharsets.UTF_8));
     }
 
-    public static void removeConsoleLayoutInfo(final String roleKey) {
-        getService(RoleService.class).removeConsoleLayoutInfo(roleKey);
+    public static void removeAnyLayout(final String roleKey) {
+        getService(RoleService.class).removeAnyLayout(roleKey);
     }
 
     public static List<String> getAllAvailableEntitlements() {
-        List<String> entitlements = new ArrayList<>(getSyncopeService().platform().getEntitlements());
-        Collections.sort(entitlements);
-        return entitlements;
+        return getSyncopeService().platform().getEntitlements().stream().sorted().collect(Collectors.toList());
     }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java
index 9acb60e..8514a34 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ExecutionsDirectoryPanel.java
@@ -95,9 +95,10 @@ public abstract class ExecutionsDirectoryPanel
     protected List<IColumn<ExecTO, String>> getColumns() {
         final List<IColumn<ExecTO, String>> columns = new ArrayList<>();
 
-        columns.add(new KeyPropertyColumn<>(new StringResourceModel("key", this), "key", "key"));
-        columns.add(new PropertyColumn<>(new StringResourceModel("executor", this), "executor", "executor"));
-        
+        columns.add(new KeyPropertyColumn<>(
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this),
+                Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME));
+
         columns.add(new DatePropertyColumn<>(new StringResourceModel("start", this), "start", "start"));
 
         columns.add(new DatePropertyColumn<>(new StringResourceModel("end", this), "end", "end"));
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java
index 0d36073..a9775e3 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java
@@ -82,7 +82,7 @@ public abstract class NotificationTaskDirectoryPanel
         final List<IColumn<NotificationTaskTO, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
 
         columns.add(new PropertyColumn<>(
                 new StringResourceModel("sender", this), "sender", "sender"));
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java
index 11e56de..71cd42c 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/PropagationTaskDirectoryPanel.java
@@ -72,7 +72,7 @@ public abstract class PropagationTaskDirectoryPanel
         final List<IColumn<PropagationTaskTO, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
 
         columns.add(new PropertyColumn<>(
                 new StringResourceModel("operation", this), "operation", "operation"));
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
index a861d4d..6b6fd0c 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/ProvisioningTaskDirectoryPanel.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.tasks;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.TaskRestClient;
 import org.apache.syncope.client.console.wicket.ajax.IndicatorAjaxTimerBehavior;
@@ -102,7 +103,7 @@ public abstract class ProvisioningTaskDirectoryPanel<T extends ProvisioningTaskT
         List<IColumn<T, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
 
         columns.add(new PropertyColumn<>(
                 new StringResourceModel("name", this), "name", "name"));
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index 983d131..5c4f0c4 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -132,7 +132,7 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
         final List<IColumn<T, String>> columns = new ArrayList<>();
 
         columns.add(new KeyPropertyColumn<>(
-                new StringResourceModel("key", this), "key"));
+                new StringResourceModel(Constants.KEY_FIELD_NAME, this), Constants.KEY_FIELD_NAME));
 
         columns.add(new PropertyColumn<>(
                 new StringResourceModel("name", this), "name", "name"));
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
index e7501a9..2c94179 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
@@ -25,6 +25,7 @@ import org.apache.syncope.client.console.panels.AjaxDataTablePanel;
 import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.rest.TaskRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.panels.ModalPanel;
 import org.apache.syncope.common.lib.to.TaskTO;
 import org.apache.syncope.common.lib.types.TaskType;
@@ -74,7 +75,7 @@ public abstract class TaskDirectoryPanel<T extends TaskTO>
         public TasksProvider(final int paginatorRows, final TaskType id) {
             super(paginatorRows);
 
-            setSort("key", SortOrder.ASCENDING);
+            setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
             this.id = id;
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java
index b82f8ba..3f11ced 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconDetailsModalPanel.java
@@ -110,7 +110,7 @@ public class ReconDetailsModalPanel extends AbstractModalPanel<Any> {
         protected List<IColumn<Misaligned, String>> getColumns() {
             List<IColumn<Misaligned, String>> columns = new ArrayList<>();
 
-            columns.add(new PropertyColumn<>(new ResourceModel("key"), "name", "name"));
+            columns.add(new PropertyColumn<>(new ResourceModel(Constants.KEY_FIELD_NAME), "name", "name"));
 
             columns.add(new AbstractColumn<Misaligned, String>(Model.of(Constants.SYNCOPE)) {
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java
index f6c3788..7387fba 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/ReconciliationWidget.java
@@ -356,7 +356,7 @@ public class ReconciliationWidget extends BaseWidget {
         protected List<IColumn<Any, String>> getColumns() {
             List<IColumn<Any, String>> columns = new ArrayList<>();
 
-            columns.add(new AbstractColumn<Any, String>(new ResourceModel("reference"), "key") {
+            columns.add(new AbstractColumn<Any, String>(new ResourceModel("reference"), Constants.KEY_FIELD_NAME) {
 
                 private static final long serialVersionUID = -1822504503325964706L;
 
@@ -452,7 +452,7 @@ public class ReconciliationWidget extends BaseWidget {
         private AnysReconciliationProvider(final Anys anys) {
             super(ROWS);
             this.anys = anys;
-            setSort("key", SortOrder.ASCENDING);
+            setSort(Constants.KEY_FIELD_NAME, SortOrder.ASCENDING);
             comparator = new SortableDataProviderComparator<>(this);
         }
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java
index b04e042..a945c3a 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/reconciliation/ReconciliationReportParser.java
@@ -28,6 +28,7 @@ import java.util.Set;
 import javax.xml.stream.XMLInputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 
 public final class ReconciliationReportParser {
@@ -68,7 +69,7 @@ public final class ReconciliationReportParser {
                     case "user":
                         user = new Any();
                         user.setType(AnyTypeKind.USER.name());
-                        user.setKey(streamReader.getAttributeValue("", "key"));
+                        user.setKey(streamReader.getAttributeValue("", Constants.KEY_FIELD_NAME));
                         user.setName(streamReader.getAttributeValue("", "username"));
                         report.getUsers().getAnys().add(user);
                         break;
@@ -82,7 +83,7 @@ public final class ReconciliationReportParser {
                     case "group":
                         group = new Any();
                         group.setType(AnyTypeKind.GROUP.name());
-                        group.setKey(streamReader.getAttributeValue("", "key"));
+                        group.setKey(streamReader.getAttributeValue("", Constants.KEY_FIELD_NAME));
                         group.setName(streamReader.getAttributeValue("", "groupName"));
                         report.getGroups().getAnys().add(group);
                         break;
@@ -98,7 +99,7 @@ public final class ReconciliationReportParser {
                     case "anyObject":
                         anyObject = new Any();
                         anyObject.setType(lastAnyType);
-                        anyObject.setKey(streamReader.getAttributeValue("", "key"));
+                        anyObject.setKey(streamReader.getAttributeValue("", Constants.KEY_FIELD_NAME));
                         final String anyType = lastAnyType;
                         Optional<Anys> anyReport = report.getAnyObjects().stream().
                                 filter(anys -> anyType.equals(anys.getAnyType())).
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java
index 71d83a8..96d4792 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Groups.java
@@ -30,6 +30,7 @@ import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.rest.GroupRestClient;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroups;
 import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroupsModel;
@@ -63,12 +64,12 @@ public class Groups extends AbstractGroups {
 
     private final List<DynRealmTO> allDynRealms = new ArrayList<>();
 
+    private final boolean templateMode;
+
     protected WebMarkupContainer dyngroupsContainer;
 
     protected WebMarkupContainer dynrealmsContainer;
 
-    private boolean templateMode;
-
     public <T extends AnyTO> Groups(final AnyWrapper<T> modelObject, final boolean templateMode) {
         super(modelObject);
         this.templateMode = templateMode;
@@ -242,8 +243,8 @@ public class Groups extends AbstractGroups {
             GroupFiqlSearchConditionBuilder searchConditionBuilder = SyncopeClient.getGroupSearchConditionBuilder();
 
             List<CompleteCondition> conditions = GroupableRelatableTO.class.cast(anyTO).getMemberships().
-                    stream().map(membership
-                            -> searchConditionBuilder.is("key").equalTo(membership.getGroupKey()).wrap()).
+                    stream().map(membership -> searchConditionBuilder.is(Constants.KEY_FIELD_NAME).
+                    equalTo(membership.getGroupKey()).wrap()).
                     collect(Collectors.toList());
 
             Map<String, GroupTO> assignedGroups = new HashMap<>();
@@ -285,8 +286,8 @@ public class Groups extends AbstractGroups {
             GroupFiqlSearchConditionBuilder searchConditionBuilder = SyncopeClient.getGroupSearchConditionBuilder();
 
             List<CompleteCondition> conditions = GroupableRelatableTO.class.cast(anyTO).getDynMemberships().
-                    stream().map(membership
-                            -> searchConditionBuilder.is("key").equalTo(membership.getGroupKey()).wrap()).
+                    stream().map(membership -> searchConditionBuilder.is(Constants.KEY_FIELD_NAME).
+                    equalTo(membership.getGroupKey()).wrap()).
                     collect(Collectors.toList());
 
             dynMemberships = new ArrayList<>();
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
index d5a6b62..8ec3b29 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/Relationships.java
@@ -346,7 +346,7 @@ public class Relationships extends WizardStep implements ICondition {
                                 anyType.getKey(),
                                 pageRef).
                                 setFiql(SyncopeClient.getAnyObjectSearchConditionBuilder(anyType.getKey()).
-                                        is("key").notNullValue().query()).
+                                        is(Constants.KEY_FIELD_NAME).notNullValue().query()).
                                 setWizardInModal(true).build("searchResultPanel");
                         fragment.add(anyObjectDirectoryPanel.setRenderBodyOnly(true));
                     }
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 341527f..3084490 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
@@ -29,6 +29,7 @@ import org.apache.syncope.client.console.rest.ApplicationRestClient;
 import org.apache.syncope.client.console.rest.DynRealmRestClient;
 import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.client.console.rest.RoleRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.console.wizards.BaseAjaxWizardBuilder;
@@ -104,7 +105,9 @@ public class RoleWizardBuilder extends BaseAjaxWizardBuilder<RoleWrapper> {
 
         public Details(final RoleWrapper modelObject) {
             add(new AjaxTextFieldPanel(
-                    "key", "key", new PropertyModel<>(modelObject.getInnerObject(), "key"), false).
+                    Constants.KEY_FIELD_NAME,
+                    Constants.KEY_FIELD_NAME,
+                    new PropertyModel<>(modelObject.getInnerObject(), Constants.KEY_FIELD_NAME), false).
                     setEnabled(StringUtils.isEmpty(modelObject.getInnerObject().getKey())));
 
             // ------------------------
@@ -194,5 +197,4 @@ public class RoleWizardBuilder extends BaseAjaxWizardBuilder<RoleWrapper> {
                     hideLabel().setOutputMarkupId(true));
         }
     }
-
 }
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.html
index fb08214..4b18d5a 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.html
@@ -60,9 +60,9 @@ under the License.
   </wicket:head>
   <wicket:panel>
     <div style="padding: 1%;">
-      <textarea wicket:id="jsonEditorInfo1" id="consoleLayoutInfo" name="jsonEditorInfo1" style="display: none;">
+      <textarea wicket:id="jsonEditorInfo1" id="jsonEditorInfo1" name="jsonEditorInfo1" style="display: none;">
       </textarea>
-      <textarea wicket:id="jsonEditorInfo2" id="consoleLayoutInfo" name="jsonEditorInfo2" style="display: none;">
+      <textarea wicket:id="jsonEditorInfo2" id="jsonEditorInfo2" name="jsonEditorInfo2" style="display: none;">
       </textarea> 
       <div class="w_content_3" id="jsonDiffEditorInfoDefForm">
 
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonEditorPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonEditorPanel.html
index 82d232e..718fd0a 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonEditorPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/JsonEditorPanel.html
@@ -60,7 +60,7 @@ under the License.
   <wicket:panel>
     <div style="padding: 1%;">
       <div class="w_content_3" id="jsonEditorInfoDefForm">
-        <textarea wicket:id="jsonEditorInfo" id="consoleLayoutInfo" name="jsonEditorInfo" style="width: 100%; height: 550px;">
+        <textarea wicket:id="jsonEditorInfo" id="jsonEditorInfo" name="jsonEditorInfo" style="width: 100%; height: 550px;">
         </textarea>
       </div>
     </div>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/XMLEditorPanel.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/XMLEditorPanel.html
index e42c0f7..d0e8224 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/XMLEditorPanel.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/XMLEditorPanel.html
@@ -46,7 +46,7 @@ under the License.
   <wicket:panel>
     <div style="padding: 1%;">
       <div class="w_content_3" id="xmlEditorInfoDefForm">
-        <textarea wicket:id="xmlEditorInfo" id="consoleLayoutInfo" name="xmlEditorInfo" style="width: 100%; height: 350px;">
+        <textarea wicket:id="xmlEditorInfo" id="xmlEditorInfo" name="xmlEditorInfo" style="width: 100%; height: 350px;">
         </textarea>
       </div>
     </div>
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/FormLayoutInfoUtils.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/AnyLayoutUtils.java
similarity index 87%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/FormLayoutInfoUtils.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/AnyLayoutUtils.java
index 283f0a0..4929901 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/FormLayoutInfoUtils.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/AnyLayoutUtils.java
@@ -34,7 +34,7 @@ import org.apache.wicket.PageReference;
 /**
  * Utility methods for dealing with form layout information.
  */
-public final class FormLayoutInfoUtils {
+public final class AnyLayoutUtils {
 
     private static final ObjectMapper MAPPER;
 
@@ -88,28 +88,29 @@ public final class FormLayoutInfoUtils {
         return result;
     }
 
-    public static ModalPanelBuilder<AnyWrapper<UserTO>> instantiate(
+    public static ModalPanelBuilder<AnyWrapper<UserTO>> newUserWizardBuilder(
             final UserTO userTO,
             final List<String> anyTypeClasses,
-            final UserFormLayoutInfo anyFormLayout,
+            final UserFormLayoutInfo userFormLayoutInfo,
             final PageReference pageRef) {
 
         try {
-            return anyFormLayout.getFormClass().getConstructor(
+            return userFormLayoutInfo.getFormClass().getConstructor(
                     userTO.getClass(), // previous
                     userTO.getClass(), // actual
                     List.class,
-                    anyFormLayout.getClass(),
+                    userFormLayoutInfo.getClass(),
                     pageRef.getClass()).
-                    newInstance(null, userTO, anyTypeClasses, anyFormLayout, pageRef);
+                    newInstance(null, userTO, anyTypeClasses, userFormLayoutInfo, pageRef);
 
         } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
                 | IllegalArgumentException | InvocationTargetException e) {
-            throw new IllegalArgumentException("Could not instantiate " + anyFormLayout.getFormClass().getName(), e);
+            throw new IllegalArgumentException(
+                    "Could not instantiate " + userFormLayoutInfo.getFormClass().getName(), e);
         }
     }
 
-    private FormLayoutInfoUtils() {
+    private AnyLayoutUtils() {
         // private constructor for static utility class
     }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Self.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Self.java
index a3a64e2..ea4c219 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Self.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Self.java
@@ -21,7 +21,7 @@ package org.apache.syncope.client.enduser.pages;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.SyncopeWebApplication;
-import org.apache.syncope.client.enduser.layout.FormLayoutInfoUtils;
+import org.apache.syncope.client.enduser.layout.AnyLayoutUtils;
 import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
@@ -92,18 +92,18 @@ public class Self extends BaseEnduserWebPage implements IEventSource {
     }
 
     protected final AjaxWizard<AnyWrapper<UserTO>> buildWizard(final UserTO userTO, final AjaxWizard.Mode mode) {
-        final String formLayoutConfParam = confParamOps.get(
+        String formLayoutConfParam = confParamOps.get(
                 SyncopeEnduserSession.get().getDomain(),
-                "enduser.form.layout.info",
-                FormLayoutInfoUtils.getDefaultValue(),
+                Constants.ENDUSER_ANYLAYOUT,
+                AnyLayoutUtils.getDefaultValue(),
                 String.class);
 
-        final UserFormLayoutInfo formLayoutInfo =
+        UserFormLayoutInfo formLayoutInfo =
                 StringUtils.isBlank(formLayoutConfParam)
                 ? new UserFormLayoutInfo()
-                : FormLayoutInfoUtils.fromJsonString(formLayoutConfParam);
+                : AnyLayoutUtils.fromJsonString(formLayoutConfParam);
 
-        wizardBuilder = (AjaxWizardBuilder<AnyWrapper<UserTO>>) FormLayoutInfoUtils.instantiate(
+        wizardBuilder = (AjaxWizardBuilder<AnyWrapper<UserTO>>) AnyLayoutUtils.newUserWizardBuilder(
                 userTO,
                 SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getUserClasses(),
                 formLayoutInfo,
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RoleRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RoleRestClient.java
index a0d055d..6b3aea3 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RoleRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RoleRestClient.java
@@ -20,9 +20,8 @@ package org.apache.syncope.client.enduser.rest;
 
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.to.RoleTO;
@@ -55,10 +54,10 @@ public class RoleRestClient extends BaseRestClient {
         return getService(RoleService.class).list();
     }
 
-    public static String readConsoleLayoutInfo(final String roleKey) {
+    public static String readAnyLayout(final String roleKey) {
         try {
             return IOUtils.toString(InputStream.class.cast(
-                    getService(RoleService.class).getConsoleLayoutInfo(roleKey).getEntity()),
+                    getService(RoleService.class).getAnyLayout(roleKey).getEntity()),
                     StandardCharsets.UTF_8);
         } catch (Exception e) {
             LOG.error("Error retrieving console layout info for role {}", roleKey, e);
@@ -66,18 +65,16 @@ public class RoleRestClient extends BaseRestClient {
         }
     }
 
-    public static void setConsoleLayoutInfo(final String roleKey, final String content) {
-        getService(RoleService.class).setConsoleLayoutInfo(
+    public static void setAnyLayout(final String roleKey, final String content) {
+        getService(RoleService.class).setAnyLayout(
                 roleKey, IOUtils.toInputStream(content, StandardCharsets.UTF_8));
     }
 
-    public static void removeConsoleLayoutInfo(final String roleKey) {
-        getService(RoleService.class).removeConsoleLayoutInfo(roleKey);
+    public static void removeAnyLayout(final String roleKey) {
+        getService(RoleService.class).removeAnyLayout(roleKey);
     }
 
     public static List<String> getAllAvailableEntitlements() {
-        List<String> entitlements = new ArrayList<>(getSyncopeService().platform().getEntitlements());
-        Collections.sort(entitlements);
-        return entitlements;
+        return getSyncopeService().platform().getEntitlements().stream().sorted().collect(Collectors.toList());
     }
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
index c4597ff..e6d3414 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/search/SpecialAttr.java
@@ -82,5 +82,4 @@ public enum SpecialAttr {
     public static Optional<SpecialAttr> fromString(final String value) {
         return Arrays.stream(values()).filter(specialAttr -> specialAttr.literal.equals(value)).findFirst();
     }
-
 }
diff --git a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RoleService.java b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RoleService.java
index 4db8fb7..5b4002c 100644
--- a/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RoleService.java
+++ b/common/idrepo/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RoleService.java
@@ -128,23 +128,23 @@ public interface RoleService extends JAXRSService {
      * @return console layout information as JSON string for the role with the given key, if available
      */
     @GET
-    @Path("{key}/consoleLayout")
+    @Path("{key}/anyLayout")
     @Produces({ MediaType.APPLICATION_JSON })
-    Response getConsoleLayoutInfo(@NotNull @PathParam("key") String key);
+    Response getAnyLayout(@NotNull @PathParam("key") String key);
 
     /**
      * Sets the console layout information as JSON string for the role with the given key, if available.
      *
      * @param key role key
-     * @param consoleLayoutInfoIn console layout information to be set
+     * @param anyLayoutIn console layout information to be set
      */
     @ApiResponses(
             @ApiResponse(responseCode = "204", description = "Operation was successful"))
     @PUT
-    @Path("{key}/consoleLayout")
+    @Path("{key}/anyLayout")
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    void setConsoleLayoutInfo(@NotNull @PathParam("key") String key, InputStream consoleLayoutInfoIn);
+    void setAnyLayout(@NotNull @PathParam("key") String key, InputStream anyLayoutIn);
 
     /**
      * Removes the console layout information for the role with the given key, if available.
@@ -154,7 +154,7 @@ public interface RoleService extends JAXRSService {
     @ApiResponses(
             @ApiResponse(responseCode = "204", description = "Operation was successful"))
     @DELETE
-    @Path("{key}/consoleLayout")
+    @Path("{key}/anyLayout")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    void removeConsoleLayoutInfo(@NotNull @PathParam("key") String key);
+    void removeAnyLayout(@NotNull @PathParam("key") String key);
 }
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 580ac15..4a55cde 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
@@ -37,7 +37,7 @@ import org.apache.syncope.core.persistence.api.dao.DuplicateException;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+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.apache.syncope.core.provisioning.api.data.RealmDataBinder;
@@ -166,9 +166,9 @@ public class RealmLogic extends AbstractTransactionalLogic<RealmTO> {
         }
 
         Set<String> adminRealms = Set.of(realm.getFullPath());
-        AnyCond keyCond = new AnyCond(AttributeCond.Type.ISNOTNULL);
+        AnyCond keyCond = new AnyCond(AttrCond.Type.ISNOTNULL);
         keyCond.setSchema("key");
-        SearchCond allMatchingCond = SearchCond.getLeafCond(keyCond);
+        SearchCond allMatchingCond = SearchCond.getLeaf(keyCond);
         int users = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.USER);
         int groups = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.GROUP);
         int anyObjects = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java
index a37e4ee..3576a15 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/RoleLogic.java
@@ -93,7 +93,7 @@ public class RoleLogic extends AbstractTransactionalLogic<RoleTO> {
     }
 
     @PreAuthorize("isAuthenticated()")
-    public String getConsoleLayoutInfo(final String key) {
+    public String getAnyLayout(final String key) {
         Role role = roleDAO.find(key);
         if (role == null) {
             LOG.error("Could not find role '" + key + '\'');
@@ -101,7 +101,7 @@ public class RoleLogic extends AbstractTransactionalLogic<RoleTO> {
             throw new NotFoundException(key);
         }
 
-        String consoleLayout = role.getConsoleLayoutInfo();
+        String consoleLayout = role.getAnyLayout();
         if (StringUtils.isBlank(consoleLayout)) {
             LOG.error("Could not find console layout for Role '" + key + '\'');
 
@@ -112,7 +112,7 @@ public class RoleLogic extends AbstractTransactionalLogic<RoleTO> {
     }
 
     @PreAuthorize("hasRole('" + IdRepoEntitlement.ROLE_UPDATE + "')")
-    public void setConsoleLayoutInfo(final String key, final String consoleLayout) {
+    public void setAnyLayout(final String key, final String consoleLayout) {
         Role role = roleDAO.find(key);
         if (role == null) {
             LOG.error("Could not find role '" + key + '\'');
@@ -120,7 +120,7 @@ public class RoleLogic extends AbstractTransactionalLogic<RoleTO> {
             throw new NotFoundException(key);
         }
 
-        role.setConsoleLayoutInfo(consoleLayout);
+        role.setAnyLayout(consoleLayout);
         roleDAO.save(role);
     }
 
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
index 58d5433..d2ef20b 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/SyncopeLogic.java
@@ -72,7 +72,7 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
 import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
@@ -444,7 +444,7 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
 
         SearchCond searchCond;
         if (StringUtils.isNotBlank(term)) {
-            AnyCond termCond = new AnyCond(AttributeCond.Type.ILIKE);
+            AnyCond termCond = new AnyCond(AttrCond.Type.ILIKE);
             termCond.setSchema("name");
 
             String termSearchableValue = (term.startsWith("*") && !term.endsWith("*"))
@@ -455,11 +455,11 @@ public class SyncopeLogic extends AbstractLogic<EntityTO> {
                     ? term : '%' + term + '%');
             termCond.setExpression(termSearchableValue);
 
-            searchCond = SearchCond.getAndCond(
-                    SearchCond.getLeafCond(assignableCond),
-                    SearchCond.getLeafCond(termCond));
+            searchCond = SearchCond.getAnd(
+                    SearchCond.getLeaf(assignableCond),
+                    SearchCond.getLeaf(termCond));
         } else {
-            searchCond = SearchCond.getLeafCond(assignableCond);
+            searchCond = SearchCond.getLeaf(assignableCond);
         }
 
         int count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCond, AnyTypeKind.GROUP);
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
index 2e2f245..7fe01c3 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
@@ -48,6 +48,7 @@ import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 
 abstract class AbstractServiceImpl implements JAXRSService {
 
@@ -64,6 +65,9 @@ abstract class AbstractServiceImpl implements JAXRSService {
     @Context
     protected SearchContext searchContext;
 
+    @Autowired
+    protected SearchCondVisitor searchCondVisitor;
+
     protected String getActualKey(final AnyDAO<?> dao, final String pretendingKey) {
         String actualKey = pretendingKey;
         if (!SyncopeConstants.UUID_PATTERN.matcher(pretendingKey).matches()) {
@@ -161,12 +165,11 @@ abstract class AbstractServiceImpl implements JAXRSService {
 
     protected SearchCond getSearchCond(final String fiql, final String realm) {
         try {
-            SearchCondVisitor visitor = new SearchCondVisitor();
-            visitor.setRealm(realm);
+            searchCondVisitor.setRealm(realm);
             SearchCondition<SearchBean> sc = searchContext.getCondition(fiql, SearchBean.class);
-            sc.accept(visitor);
+            sc.accept(searchCondVisitor);
 
-            return visitor.getQuery();
+            return searchCondVisitor.getQuery();
         } catch (Exception e) {
             LOG.error("Invalid FIQL expression: {}", fiql, e);
 
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RoleServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RoleServiceImpl.java
index 6a0a75a..961ba63 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RoleServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RoleServiceImpl.java
@@ -71,8 +71,8 @@ public class RoleServiceImpl extends AbstractServiceImpl implements RoleService
     }
 
     @Override
-    public Response getConsoleLayoutInfo(final String key) {
-        String template = logic.getConsoleLayoutInfo(key);
+    public Response getAnyLayout(final String key) {
+        String template = logic.getAnyLayout(key);
         StreamingOutput sout = (os) -> os.write(template.getBytes());
 
         return Response.ok(sout).
@@ -81,9 +81,9 @@ public class RoleServiceImpl extends AbstractServiceImpl implements RoleService
     }
 
     @Override
-    public void setConsoleLayoutInfo(final String key, final InputStream consoleLayoutIn) {
+    public void setAnyLayout(final String key, final InputStream anyLayoutIn) {
         try {
-            logic.setConsoleLayoutInfo(key, IOUtils.toString(consoleLayoutIn, StandardCharsets.UTF_8.name()));
+            logic.setAnyLayout(key, IOUtils.toString(anyLayoutIn, StandardCharsets.UTF_8.name()));
         } catch (final IOException e) {
             LOG.error("While setting console layout info for role {}", key, e);
             throw new InternalServerErrorException("Could not read entity", e);
@@ -91,8 +91,7 @@ public class RoleServiceImpl extends AbstractServiceImpl implements RoleService
     }
 
     @Override
-    public void removeConsoleLayoutInfo(final String key) {
-        logic.setConsoleLayoutInfo(key, null);
+    public void removeAnyLayout(final String key) {
+        logic.setAnyLayout(key, null);
     }
-
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AnyCond.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AnyCond.java
index 55736fa..c7200eb 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AnyCond.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AnyCond.java
@@ -23,7 +23,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
 /**
  * Search condition to be applied when comparing bean field values.
  */
-public class AnyCond extends AttributeCond {
+public class AnyCond extends AttrCond {
 
     private static final long serialVersionUID = -1880319220462653955L;
 
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AttributeCond.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AttrCond.java
similarity index 94%
rename from core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AttributeCond.java
rename to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AttrCond.java
index c008f69..73d8a1f 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AttributeCond.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/AttrCond.java
@@ -25,7 +25,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
 /**
  * Search condition to be applied when comparing attribute values.
  */
-public class AttributeCond extends AbstractSearchCond {
+public class AttrCond extends AbstractSearchCond {
 
     private static final long serialVersionUID = 3275277728404021417L;
 
@@ -50,11 +50,11 @@ public class AttributeCond extends AbstractSearchCond {
 
     private String expression;
 
-    public AttributeCond() {
+    public AttrCond() {
         super();
     }
 
-    public AttributeCond(final Type conditionType) {
+    public AttrCond(final Type conditionType) {
         super();
         this.type = conditionType;
     }
@@ -108,7 +108,7 @@ public class AttributeCond extends AbstractSearchCond {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final AttributeCond other = (AttributeCond) obj;
+        final AttrCond other = (AttrCond) obj;
         return new EqualsBuilder().
                 append(type, other.type).
                 append(schema, other.schema).
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java
index f4c1630..8d77301 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/search/SearchCond.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.api.dao.search;
 
 import java.util.List;
+import java.util.Optional;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -38,232 +39,78 @@ public class SearchCond extends AbstractSearchCond {
 
     private Type type;
 
-    private AnyTypeCond anyTypeCond;
+    private AbstractSearchCond leaf;
 
-    private AnyCond anyCond;
+    private SearchCond left;
 
-    private AttributeCond attributeCond;
+    private SearchCond right;
 
-    private RelationshipCond relationshipCond;
-
-    private RelationshipTypeCond relationshipTypeCond;
-
-    private MembershipCond membershipCond;
-
-    private RoleCond roleCond;
-
-    private PrivilegeCond privilegeCond;
-
-    private DynRealmCond dynRealmCond;
-
-    private ResourceCond resourceCond;
-
-    private AssignableCond assignableCond;
-
-    private MemberCond memberCond;
-
-    private SearchCond leftSearchCond;
-
-    private SearchCond rightSearchCond;
-
-    public static SearchCond getLeafCond(final AnyTypeCond anyTypeCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.anyTypeCond = anyTypeCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final AttributeCond attributeCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        if (attributeCond instanceof AnyCond) {
-            nodeCond.anyCond = (AnyCond) attributeCond;
+    public static SearchCond getLeaf(final AbstractSearchCond leaf) {
+        SearchCond cond;
+        if (leaf instanceof SearchCond) {
+            cond = (SearchCond) leaf;
         } else {
-            nodeCond.attributeCond = attributeCond;
+            cond = new SearchCond();
+            cond.leaf = leaf;
         }
 
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final RelationshipCond relationshipCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.relationshipCond = relationshipCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final RelationshipTypeCond relationshipTypeCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.relationshipTypeCond = relationshipTypeCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final MembershipCond membershipCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.membershipCond = membershipCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final RoleCond roleCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.roleCond = roleCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final PrivilegeCond privilegeCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.privilegeCond = privilegeCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final DynRealmCond dynRealmCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.dynRealmCond = dynRealmCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getLeafCond(final ResourceCond resourceCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.LEAF;
-        nodeCond.resourceCond = resourceCond;
+        cond.type = Type.LEAF;
 
-        return nodeCond;
+        return cond;
     }
 
-    public static SearchCond getLeafCond(final AssignableCond assignableCond) {
-        SearchCond nodeCond = new SearchCond();
+    public static SearchCond getNotLeaf(final AbstractSearchCond leaf) {
+        SearchCond cond = getLeaf(leaf);
 
-        nodeCond.type = Type.LEAF;
-        nodeCond.assignableCond = assignableCond;
+        cond.type = Type.NOT_LEAF;
 
-        return nodeCond;
+        return cond;
     }
 
-    public static SearchCond getLeafCond(final MemberCond memberCond) {
-        SearchCond nodeCond = new SearchCond();
+    public static SearchCond getAnd(final SearchCond left, final SearchCond right) {
+        SearchCond cond = new SearchCond();
 
-        nodeCond.type = Type.LEAF;
-        nodeCond.memberCond = memberCond;
+        cond.type = Type.AND;
+        cond.left = left;
+        cond.right = right;
 
-        return nodeCond;
+        return cond;
     }
 
-    public static SearchCond getNotLeafCond(final AttributeCond attributeCond) {
-        SearchCond nodeCond = getLeafCond(attributeCond);
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getNotLeafCond(final RelationshipCond relationshipCond) {
-        SearchCond nodeCond = getLeafCond(relationshipCond);
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getNotLeafCond(final MembershipCond membershipCond) {
-        SearchCond nodeCond = getLeafCond(membershipCond);
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getNotLeafCond(final RoleCond roleCond) {
-        SearchCond nodeCond = getLeafCond(roleCond);
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getNotLeafCond(final PrivilegeCond privilegeCond) {
-        SearchCond nodeCond = getLeafCond(privilegeCond);
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getNotLeafCond(final ResourceCond resourceCond) {
-        SearchCond nodeCond = getLeafCond(resourceCond);
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getNotLeafCond(final MemberCond memberCond) {
-        SearchCond nodeCond = getLeafCond(memberCond);
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getNotLeafCond(final SearchCond nodeCond) {
-        nodeCond.type = Type.NOT_LEAF;
-        return nodeCond;
-    }
-
-    public static SearchCond getAndCond(final SearchCond leftCond, final SearchCond rightCond) {
-        SearchCond nodeCond = new SearchCond();
-
-        nodeCond.type = Type.AND;
-        nodeCond.leftSearchCond = leftCond;
-        nodeCond.rightSearchCond = rightCond;
-
-        return nodeCond;
-    }
-
-    public static SearchCond getAndCond(final List<SearchCond> conditions) {
+    public static SearchCond getAnd(final List<SearchCond> conditions) {
         if (conditions.size() == 1) {
             return conditions.get(0);
         } else if (conditions.size() > 2) {
             SearchCond removed = conditions.remove(0);
-            return getAndCond(removed, getAndCond(conditions));
+            return getAnd(removed, getAnd(conditions));
         } else {
-            return getAndCond(conditions.get(0), conditions.get(1));
+            return getAnd(conditions.get(0), conditions.get(1));
         }
     }
 
-    public static SearchCond getOrCond(final SearchCond leftCond, final SearchCond rightCond) {
-        SearchCond nodeCond = new SearchCond();
+    public static SearchCond getOr(final SearchCond left, final SearchCond right) {
+        SearchCond cond = new SearchCond();
 
-        nodeCond.type = Type.OR;
-        nodeCond.leftSearchCond = leftCond;
-        nodeCond.rightSearchCond = rightCond;
+        cond.type = Type.OR;
+        cond.left = left;
+        cond.right = right;
 
-        return nodeCond;
+        return cond;
     }
 
-    public static SearchCond getOrCond(final List<SearchCond> conditions) {
+    public static SearchCond getOr(final List<SearchCond> conditions) {
         if (conditions.size() == 1) {
             return conditions.get(0);
         } else if (conditions.size() > 2) {
             SearchCond removed = conditions.remove(0);
-            return getOrCond(removed, getOrCond(conditions));
+            return getOr(removed, getOr(conditions));
         } else {
-            return getOrCond(conditions.get(0), conditions.get(1));
+            return getOr(conditions.get(0), conditions.get(1));
         }
     }
 
-    public AnyTypeCond getAnyTypeCond() {
-        return anyTypeCond;
-    }
-
-    public void setAnyTypeCond(final AnyTypeCond anyTypeCond) {
-        this.anyTypeCond = anyTypeCond;
+    public Optional<AnyTypeCond> getAnyTypeCond() {
+        return Optional.ofNullable(leaf instanceof AnyTypeCond ? (AnyTypeCond) leaf : null);
     }
 
     /**
@@ -281,18 +128,18 @@ public class SearchCond extends AbstractSearchCond {
         switch (type) {
             case LEAF:
             case NOT_LEAF:
-                if (anyTypeCond != null) {
-                    anyTypeName = anyTypeCond.getAnyTypeKey();
+                if (leaf instanceof AnyTypeCond) {
+                    anyTypeName = ((AnyTypeCond) leaf).getAnyTypeKey();
                 }
                 break;
 
             case AND:
             case OR:
-                if (leftSearchCond != null) {
-                    anyTypeName = leftSearchCond.hasAnyTypeCond();
+                if (left != null) {
+                    anyTypeName = left.hasAnyTypeCond();
                 }
-                if (anyTypeName == null && rightSearchCond != null) {
-                    anyTypeName = rightSearchCond.hasAnyTypeCond();
+                if (anyTypeName == null && right != null) {
+                    anyTypeName = right.hasAnyTypeCond();
                 }
                 break;
 
@@ -302,56 +149,17 @@ public class SearchCond extends AbstractSearchCond {
         return anyTypeName;
     }
 
-    public AnyCond getAnyCond() {
-        return anyCond;
-    }
-
-    public AttributeCond getAttributeCond() {
-        return attributeCond;
-    }
-
-    public RelationshipCond getRelationshipCond() {
-        return relationshipCond;
-    }
-
-    public RelationshipTypeCond getRelationshipTypeCond() {
-        return relationshipTypeCond;
-    }
-
-    public MembershipCond getMembershipCond() {
-        return membershipCond;
-    }
-
-    public RoleCond getRoleCond() {
-        return roleCond;
-    }
-
-    public PrivilegeCond getPrivilegeCond() {
-        return privilegeCond;
-    }
-
-    public DynRealmCond getDynRealmCond() {
-        return dynRealmCond;
-    }
-
-    public ResourceCond getResourceCond() {
-        return resourceCond;
-    }
-
-    public AssignableCond getAssignableCond() {
-        return assignableCond;
-    }
-
-    public MemberCond getMemberCond() {
-        return memberCond;
+    @SuppressWarnings("unchecked")
+    public <T extends AbstractSearchCond> Optional<T> getLeaf(final Class<T> clazz) {
+        return Optional.ofNullable((T) (clazz.isInstance(leaf) ? leaf : null));
     }
 
-    public SearchCond getLeftSearchCond() {
-        return leftSearchCond;
+    public SearchCond getLeft() {
+        return left;
     }
 
-    public SearchCond getRightSearchCond() {
-        return rightSearchCond;
+    public SearchCond getRight() {
+        return right;
     }
 
     public Type getType() {
@@ -369,25 +177,13 @@ public class SearchCond extends AbstractSearchCond {
         switch (type) {
             case LEAF:
             case NOT_LEAF:
-                isValid = (anyTypeCond != null || anyCond != null || attributeCond != null || dynRealmCond != null
-                        || relationshipCond != null || relationshipTypeCond != null || membershipCond != null
-                        || roleCond != null || privilegeCond != null || resourceCond != null
-                        || assignableCond != null || memberCond != null)
-                        && (anyTypeCond == null || anyTypeCond.isValid())
-                        && (anyCond == null || anyCond.isValid())
-                        && (attributeCond == null || attributeCond.isValid())
-                        && (membershipCond == null || membershipCond.isValid())
-                        && (roleCond == null || roleCond.isValid())
-                        && (privilegeCond == null || privilegeCond.isValid())
-                        && (resourceCond == null || resourceCond.isValid())
-                        && (memberCond == null || memberCond.isValid());
+                isValid = leaf != null && leaf.isValid();
                 break;
 
             case AND:
             case OR:
-                isValid = (leftSearchCond == null || rightSearchCond == null)
-                        ? false
-                        : leftSearchCond.isValid() && rightSearchCond.isValid();
+                isValid = left != null && right != null
+                        && left.isValid() && right.isValid();
                 break;
 
             default:
@@ -400,20 +196,9 @@ public class SearchCond extends AbstractSearchCond {
     public int hashCode() {
         return new HashCodeBuilder().
                 append(type).
-                append(anyTypeCond).
-                append(anyCond).
-                append(attributeCond).
-                append(relationshipCond).
-                append(relationshipTypeCond).
-                append(membershipCond).
-                append(roleCond).
-                append(privilegeCond).
-                append(dynRealmCond).
-                append(resourceCond).
-                append(assignableCond).
-                append(memberCond).
-                append(leftSearchCond).
-                append(rightSearchCond).
+                append(leaf).
+                append(left).
+                append(right).
                 build();
     }
 
@@ -431,20 +216,9 @@ public class SearchCond extends AbstractSearchCond {
         final SearchCond other = (SearchCond) obj;
         return new EqualsBuilder().
                 append(type, other.type).
-                append(anyTypeCond, other.anyTypeCond).
-                append(anyCond, other.anyCond).
-                append(attributeCond, other.attributeCond).
-                append(relationshipCond, other.relationshipCond).
-                append(relationshipTypeCond, other.relationshipTypeCond).
-                append(membershipCond, other.membershipCond).
-                append(roleCond, other.roleCond).
-                append(privilegeCond, other.privilegeCond).
-                append(dynRealmCond, other.dynRealmCond).
-                append(resourceCond, other.resourceCond).
-                append(assignableCond, other.assignableCond).
-                append(memberCond, other.memberCond).
-                append(leftSearchCond, other.leftSearchCond).
-                append(rightSearchCond, other.rightSearchCond).
+                append(leaf, other.leaf).
+                append(left, other.left).
+                append(right, other.right).
                 build();
     }
 
@@ -452,20 +226,9 @@ public class SearchCond extends AbstractSearchCond {
     public String toString() {
         return new ToStringBuilder(this).
                 append(type).
-                append(anyTypeCond).
-                append(anyCond).
-                append(attributeCond).
-                append(relationshipCond).
-                append(relationshipTypeCond).
-                append(membershipCond).
-                append(roleCond).
-                append(privilegeCond).
-                append(dynRealmCond).
-                append(resourceCond).
-                append(assignableCond).
-                append(memberCond).
-                append(leftSearchCond).
-                append(rightSearchCond).
+                append(leaf).
+                append(left).
+                append(right).
                 build();
     }
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java
index e4cf8a2..20e1521 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Role.java
@@ -38,9 +38,9 @@ public interface Role extends ProvidedKeyEntity {
 
     void setDynMembership(DynRoleMembership dynMembership);
 
-    String getConsoleLayoutInfo();
+    String getAnyLayout();
 
-    void setConsoleLayoutInfo(String consoleLayoutInfo);
+    void setAnyLayout(String anyLayout);
 
     boolean add(Privilege privilege);
 
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 7c216b3..da4095c 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,16 +37,16 @@ public final class SearchCondConverter {
     /**
      * Parses a FIQL expression into Syncope's {@link SearchCond}, using {@link SyncopeFiqlParser}.
      *
+     * @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 String fiql, final String... realms) {
+    public static SearchCond convert(final SearchCondVisitor visitor, final String fiql, final String... realms) {
         SyncopeFiqlParser<SearchBean> parser = new SyncopeFiqlParser<>(
                 SearchBean.class, AbstractFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
 
         try {
-            SearchCondVisitor visitor = new SearchCondVisitor();
             if (realms != null && realms.length > 0) {
                 visitor.setRealm(realms[0]);
             }
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/SearchCondVisitor.java
index 616a195..bdf9d21 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/SearchCondVisitor.java
@@ -33,7 +33,6 @@ import org.apache.syncope.common.lib.search.SearchableFields;
 import org.apache.syncope.common.lib.search.SpecialAttr;
 import org.apache.syncope.common.lib.search.SyncopeFiqlParser;
 import org.apache.syncope.common.lib.search.SyncopeFiqlSearchCondition;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
 import org.apache.syncope.core.persistence.api.dao.search.ResourceCond;
 import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
@@ -41,6 +40,7 @@ import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 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.AssignableCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
 import org.apache.syncope.core.persistence.api.dao.search.PrivilegeCond;
@@ -52,33 +52,29 @@ import org.apache.syncope.core.persistence.api.dao.search.RelationshipTypeCond;
  */
 public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean, SearchCond> {
 
-    private static final Pattern TIMEZONE = Pattern.compile(".* [0-9]{4}$");
+    protected static final Pattern TIMEZONE = Pattern.compile(".* [0-9]{4}$");
 
-    private String realm;
+    protected static final ThreadLocal<String> REALM = new ThreadLocal<>();
 
-    private SearchCond searchCond;
+    protected static final ThreadLocal<SearchCond> SEARCH_COND = new ThreadLocal<>();
 
     public SearchCondVisitor() {
         super(null);
     }
 
     public void setRealm(final String realm) {
-        this.realm = realm;
+        REALM.set(realm);
     }
 
-    private static AttributeCond createAttributeCond(final String schema) {
-        AttributeCond attributeCond = SearchableFields.contains(schema)
+    protected static AttrCond createAttrCond(final String schema) {
+        AttrCond attrCond = SearchableFields.contains(schema)
                 ? new AnyCond()
-                : new AttributeCond();
-        attributeCond.setSchema(schema);
-        return attributeCond;
+                : new AttrCond();
+        attrCond.setSchema(schema);
+        return attrCond;
     }
 
-    @SuppressWarnings("ConvertToStringSwitch")
-    private SearchCond visitPrimitive(final SearchCondition<SearchBean> sc) {
-        String name = getRealPropertyName(sc.getStatement().getProperty());
-        Optional<SpecialAttr> specialAttrName = SpecialAttr.fromString(name);
-
+    protected static String getValue(final SearchCondition<SearchBean> sc) {
         String value = SearchUtils.toSqlWildcardString(
                 URLDecoder.decode(sc.getStatement().getValue().toString(), StandardCharsets.UTF_8), false).
                 replaceAll("\\\\_", "_");
@@ -90,11 +86,10 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
             value = new String(valueAsArray);
         }
 
-        Optional<SpecialAttr> specialAttrValue = SpecialAttr.fromString(value);
-
-        AttributeCond attributeCond = createAttributeCond(name);
-        attributeCond.setExpression(value);
+        return value;
+    }
 
+    protected static ConditionType getConditionType(final SearchCondition<SearchBean> sc) {
         ConditionType ct = sc.getConditionType();
         if (sc instanceof SyncopeFiqlSearchCondition && sc.getConditionType() == ConditionType.CUSTOM) {
             SyncopeFiqlSearchCondition<SearchBean> sfsc = (SyncopeFiqlSearchCondition<SearchBean>) sc;
@@ -113,85 +108,101 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
             }
         }
 
+        return ct;
+    }
+
+    @SuppressWarnings("ConvertToStringSwitch")
+    protected SearchCond visitPrimitive(final SearchCondition<SearchBean> sc) {
+        String name = getRealPropertyName(sc.getStatement().getProperty());
+        Optional<SpecialAttr> specialAttrName = SpecialAttr.fromString(name);
+
+        String value = getValue(sc);
+        Optional<SpecialAttr> specialAttrValue = SpecialAttr.fromString(value);
+
+        AttrCond attrCond = createAttrCond(name);
+        attrCond.setExpression(value);
+
+        ConditionType ct = getConditionType(sc);
+
         SearchCond leaf;
         switch (ct) {
             case EQUALS:
             case NOT_EQUALS:
-                if (specialAttrName.isEmpty()) {
+                if (!specialAttrName.isPresent()) {
                     if (specialAttrValue.isPresent() && specialAttrValue.get() == SpecialAttr.NULL) {
-                        attributeCond.setType(AttributeCond.Type.ISNULL);
-                        attributeCond.setExpression(null);
+                        attrCond.setType(AttrCond.Type.ISNULL);
+                        attrCond.setExpression(null);
                     } else if (value.indexOf('%') == -1) {
-                        attributeCond.setType(sc.getConditionType() == ConditionType.CUSTOM
-                                ? AttributeCond.Type.IEQ
-                                : AttributeCond.Type.EQ);
+                        attrCond.setType(sc.getConditionType() == ConditionType.CUSTOM
+                                ? AttrCond.Type.IEQ
+                                : AttrCond.Type.EQ);
                     } else {
-                        attributeCond.setType(sc.getConditionType() == ConditionType.CUSTOM
-                                ? AttributeCond.Type.ILIKE
-                                : AttributeCond.Type.LIKE);
+                        attrCond.setType(sc.getConditionType() == ConditionType.CUSTOM
+                                ? AttrCond.Type.ILIKE
+                                : AttrCond.Type.LIKE);
                     }
 
-                    leaf = SearchCond.getLeafCond(attributeCond);
+                    leaf = SearchCond.getLeaf(attrCond);
                 } else {
                     switch (specialAttrName.get()) {
                         case TYPE:
                             AnyTypeCond typeCond = new AnyTypeCond();
                             typeCond.setAnyTypeKey(value);
-                            leaf = SearchCond.getLeafCond(typeCond);
+                            leaf = SearchCond.getLeaf(typeCond);
                             break;
 
                         case RESOURCES:
                             ResourceCond resourceCond = new ResourceCond();
                             resourceCond.setResourceKey(value);
-                            leaf = SearchCond.getLeafCond(resourceCond);
+                            leaf = SearchCond.getLeaf(resourceCond);
                             break;
 
                         case GROUPS:
                             MembershipCond groupCond = new MembershipCond();
                             groupCond.setGroup(value);
-                            leaf = SearchCond.getLeafCond(groupCond);
+                            leaf = SearchCond.getLeaf(groupCond);
                             break;
 
                         case RELATIONSHIPS:
                             RelationshipCond relationshipCond = new RelationshipCond();
                             relationshipCond.setAnyObject(value);
-                            leaf = SearchCond.getLeafCond(relationshipCond);
+                            leaf = SearchCond.getLeaf(relationshipCond);
                             break;
 
                         case RELATIONSHIP_TYPES:
                             RelationshipTypeCond relationshipTypeCond = new RelationshipTypeCond();
                             relationshipTypeCond.setRelationshipTypeKey(value);
-                            leaf = SearchCond.getLeafCond(relationshipTypeCond);
+                            leaf = SearchCond.getLeaf(relationshipTypeCond);
                             break;
 
                         case ROLES:
                             RoleCond roleCond = new RoleCond();
                             roleCond.setRole(value);
-                            leaf = SearchCond.getLeafCond(roleCond);
+                            leaf = SearchCond.getLeaf(roleCond);
                             break;
 
                         case PRIVILEGES:
                             PrivilegeCond privilegeCond = new PrivilegeCond();
                             privilegeCond.setPrivilege(value);
-                            leaf = SearchCond.getLeafCond(privilegeCond);
+                            leaf = SearchCond.getLeaf(privilegeCond);
                             break;
 
                         case DYNREALMS:
                             DynRealmCond dynRealmCond = new DynRealmCond();
                             dynRealmCond.setDynRealm(value);
-                            leaf = SearchCond.getLeafCond(dynRealmCond);
+                            leaf = SearchCond.getLeaf(dynRealmCond);
                             break;
 
                         case ASSIGNABLE:
                             AssignableCond assignableCond = new AssignableCond();
-                            assignableCond.setRealmFullPath(realm);
-                            leaf = SearchCond.getLeafCond(assignableCond);
+                            assignableCond.setRealmFullPath(REALM.get());
+                            leaf = SearchCond.getLeaf(assignableCond);
                             break;
 
                         case MEMBER:
                             MemberCond memberCond = new MemberCond();
                             memberCond.setMember(value);
-                            leaf = SearchCond.getLeafCond(memberCond);
+                            leaf = SearchCond.getLeaf(memberCond);
                             break;
 
                         default:
@@ -200,38 +211,33 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
                     }
                 }
                 if (ct == ConditionType.NOT_EQUALS) {
-                    if (leaf.getAttributeCond() != null
-                            && leaf.getAttributeCond().getType() == AttributeCond.Type.ISNULL) {
-
-                        leaf.getAttributeCond().setType(AttributeCond.Type.ISNOTNULL);
-                    } else if (leaf.getAnyCond() != null
-                            && leaf.getAnyCond().getType() == AttributeCond.Type.ISNULL) {
-
-                        leaf.getAnyCond().setType(AttributeCond.Type.ISNOTNULL);
+                    Optional<AttrCond> notEquals = leaf.getLeaf(AttrCond.class);
+                    if (notEquals.isPresent() && notEquals.get().getType() == AttrCond.Type.ISNULL) {
+                        notEquals.get().setType(AttrCond.Type.ISNOTNULL);
                     } else {
-                        leaf = SearchCond.getNotLeafCond(leaf);
+                        leaf = SearchCond.getNotLeaf(leaf);
                     }
                 }
                 break;
 
             case GREATER_OR_EQUALS:
-                attributeCond.setType(AttributeCond.Type.GE);
-                leaf = SearchCond.getLeafCond(attributeCond);
+                attrCond.setType(AttrCond.Type.GE);
+                leaf = SearchCond.getLeaf(attrCond);
                 break;
 
             case GREATER_THAN:
-                attributeCond.setType(AttributeCond.Type.GT);
-                leaf = SearchCond.getLeafCond(attributeCond);
+                attrCond.setType(AttrCond.Type.GT);
+                leaf = SearchCond.getLeaf(attrCond);
                 break;
 
             case LESS_OR_EQUALS:
-                attributeCond.setType(AttributeCond.Type.LE);
-                leaf = SearchCond.getLeafCond(attributeCond);
+                attrCond.setType(AttrCond.Type.LE);
+                leaf = SearchCond.getLeaf(attrCond);
                 break;
 
             case LESS_THAN:
-                attributeCond.setType(AttributeCond.Type.LT);
-                leaf = SearchCond.getLeafCond(attributeCond);
+                attrCond.setType(AttrCond.Type.LT);
+                leaf = SearchCond.getLeaf(attrCond);
                 break;
 
             default:
@@ -239,36 +245,37 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
         }
 
         // SYNCOPE-1293: explicitly re-process to allow 'token==$null' or 'token!=$null'
-        if (leaf.getAttributeCond() != null
-                && "token".equals(leaf.getAttributeCond().getSchema())
-                && (leaf.getAttributeCond().getType() == AttributeCond.Type.ISNULL
-                || leaf.getAttributeCond().getType() == AttributeCond.Type.ISNOTNULL)
-                && leaf.getAttributeCond().getExpression() == null) {
-
+        Optional<AttrCond> reprocess = leaf.getLeaf(AttrCond.class).
+                filter(cond -> "token".equals(cond.getSchema())
+                && (cond.getType() == AttrCond.Type.ISNULL || cond.getType() == AttrCond.Type.ISNOTNULL)
+                && cond.getExpression() == null);
+        if (reprocess.isPresent()) {
             AnyCond tokenCond = new AnyCond();
-            tokenCond.setSchema(leaf.getAttributeCond().getSchema());
-            tokenCond.setType(leaf.getAttributeCond().getType());
+            tokenCond.setSchema(reprocess.get().getSchema());
+            tokenCond.setType(reprocess.get().getType());
             tokenCond.setExpression(null);
-            leaf = SearchCond.getLeafCond(tokenCond);
+            leaf = SearchCond.getLeaf(tokenCond);
         }
 
         return leaf;
     }
 
-    private SearchCond visitCompount(final SearchCondition<SearchBean> sc) {
+    protected SearchCond visitCompount(final SearchCondition<SearchBean> sc) {
         List<SearchCond> searchConds = new ArrayList<>();
-        sc.getSearchConditions().forEach(searchCond -> searchConds.add(searchCond.getStatement() == null
-                ? visitCompount(searchCond)
-                : visitPrimitive(searchCond)));
+        sc.getSearchConditions().forEach(searchCond -> {
+            searchConds.add(searchCond.getStatement() == null
+                    ? visitCompount(searchCond)
+                    : visitPrimitive(searchCond));
+        });
 
         SearchCond compound;
         switch (sc.getConditionType()) {
             case AND:
-                compound = SearchCond.getAndCond(searchConds);
+                compound = SearchCond.getAnd(searchConds);
                 break;
 
             case OR:
-                compound = SearchCond.getOrCond(searchConds);
+                compound = SearchCond.getOr(searchConds);
                 break;
 
             default:
@@ -281,13 +288,11 @@ public class SearchCondVisitor extends AbstractSearchConditionVisitor<SearchBean
 
     @Override
     public void visit(final SearchCondition<SearchBean> sc) {
-        searchCond = sc.getStatement() == null
-                ? visitCompount(sc)
-                : visitPrimitive(sc);
+        SEARCH_COND.set(sc.getStatement() == null ? visitCompount(sc) : visitPrimitive(sc));
     }
 
     @Override
     public SearchCond getQuery() {
-        return searchCond;
+        return SEARCH_COND.get();
     }
 }
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 8e4946c..eafb313 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
@@ -25,7 +25,6 @@ import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
 import org.apache.syncope.common.lib.search.GroupFiqlSearchConditionBuilder;
 import org.apache.syncope.common.lib.search.SpecialAttr;
 import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
 import org.apache.syncope.core.persistence.api.dao.search.ResourceCond;
 import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
@@ -33,6 +32,7 @@ import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 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.AssignableCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
 import org.apache.syncope.core.persistence.api.dao.search.PrivilegeCond;
@@ -42,17 +42,19 @@ import org.junit.jupiter.api.Test;
 
 public class SearchCondConverterTest {
 
+    private static final SearchCondVisitor VISITOR = new SearchCondVisitor();
+
     @Test
     public void eq() {
         String fiql = new UserFiqlSearchConditionBuilder().is("username").equalTo("rossini").query();
         assertEquals("username==rossini", fiql);
 
-        AnyCond attrCond = new AnyCond(AttributeCond.Type.EQ);
+        AnyCond attrCond = new AnyCond(AttrCond.Type.EQ);
         attrCond.setSchema("username");
         attrCond.setExpression("rossini");
-        SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+        SearchCond leaf = SearchCond.getLeaf(attrCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -60,12 +62,12 @@ public class SearchCondConverterTest {
         String fiql = new UserFiqlSearchConditionBuilder().is("username").equalToIgnoreCase("rossini").query();
         assertEquals("username=~rossini", fiql);
 
-        AnyCond attrCond = new AnyCond(AttributeCond.Type.IEQ);
+        AnyCond attrCond = new AnyCond(AttrCond.Type.IEQ);
         attrCond.setSchema("username");
         attrCond.setExpression("rossini");
-        SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+        SearchCond leaf = SearchCond.getLeaf(attrCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -73,12 +75,12 @@ public class SearchCondConverterTest {
         String fiql = new UserFiqlSearchConditionBuilder().is("username").notEqualTolIgnoreCase("rossini").query();
         assertEquals("username!~rossini", fiql);
 
-        AnyCond attrCond = new AnyCond(AttributeCond.Type.IEQ);
-        attrCond.setSchema("username");
-        attrCond.setExpression("rossini");
-        SearchCond simpleCond = SearchCond.getNotLeafCond(attrCond);
+        AnyCond anyCond = new AnyCond(AttrCond.Type.IEQ);
+        anyCond.setSchema("username");
+        anyCond.setExpression("rossini");
+        SearchCond leaf = SearchCond.getNotLeaf(anyCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -86,12 +88,12 @@ public class SearchCondConverterTest {
         String fiql = new UserFiqlSearchConditionBuilder().is("username").equalTo("ros*").query();
         assertEquals("username==ros*", fiql);
 
-        AttributeCond attrCond = new AnyCond(AttributeCond.Type.LIKE);
+        AttrCond attrCond = new AnyCond(AttrCond.Type.LIKE);
         attrCond.setSchema("username");
         attrCond.setExpression("ros%");
-        SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+        SearchCond leaf = SearchCond.getLeaf(attrCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -99,12 +101,12 @@ public class SearchCondConverterTest {
         String fiql = new UserFiqlSearchConditionBuilder().is("username").equalToIgnoreCase("ros*").query();
         assertEquals("username=~ros*", fiql);
 
-        AttributeCond attrCond = new AnyCond(AttributeCond.Type.ILIKE);
+        AttrCond attrCond = new AnyCond(AttrCond.Type.ILIKE);
         attrCond.setSchema("username");
         attrCond.setExpression("ros%");
-        SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+        SearchCond leaf = SearchCond.getLeaf(attrCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -112,12 +114,12 @@ public class SearchCondConverterTest {
         String fiql = new UserFiqlSearchConditionBuilder().is("username").notEqualTolIgnoreCase("ros*").query();
         assertEquals("username!~ros*", fiql);
 
-        AttributeCond attrCond = new AnyCond(AttributeCond.Type.ILIKE);
+        AttrCond attrCond = new AnyCond(AttrCond.Type.ILIKE);
         attrCond.setSchema("username");
         attrCond.setExpression("ros%");
-        SearchCond simpleCond = SearchCond.getNotLeafCond(attrCond);
+        SearchCond leaf = SearchCond.getNotLeaf(attrCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -125,11 +127,11 @@ public class SearchCondConverterTest {
         String fiql = new UserFiqlSearchConditionBuilder().is("loginDate").nullValue().query();
         assertEquals("loginDate==" + SpecialAttr.NULL, fiql);
 
-        AttributeCond attrCond = new AttributeCond(AttributeCond.Type.ISNULL);
+        AttrCond attrCond = new AttrCond(AttrCond.Type.ISNULL);
         attrCond.setSchema("loginDate");
-        SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+        SearchCond leaf = SearchCond.getLeaf(attrCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -137,11 +139,11 @@ public class SearchCondConverterTest {
         String fiql = new UserFiqlSearchConditionBuilder().is("loginDate").notNullValue().query();
         assertEquals("loginDate!=" + SpecialAttr.NULL, fiql);
 
-        AttributeCond attrCond = new AttributeCond(AttributeCond.Type.ISNOTNULL);
+        AttrCond attrCond = new AttrCond(AttrCond.Type.ISNOTNULL);
         attrCond.setSchema("loginDate");
-        SearchCond simpleCond = SearchCond.getLeafCond(attrCond);
+        SearchCond leaf = SearchCond.getLeaf(attrCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -152,9 +154,9 @@ public class SearchCondConverterTest {
 
         RelationshipCond relationshipCond = new RelationshipCond();
         relationshipCond.setAnyObject("ca20ffca-1305-442f-be9a-3723a0cd88ca");
-        SearchCond simpleCond = SearchCond.getLeafCond(relationshipCond);
+        SearchCond leaf = SearchCond.getLeaf(relationshipCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -164,9 +166,9 @@ public class SearchCondConverterTest {
 
         RelationshipTypeCond relationshipCond = new RelationshipTypeCond();
         relationshipCond.setRelationshipTypeKey("type1");
-        SearchCond simpleCond = SearchCond.getLeafCond(relationshipCond);
+        SearchCond leaf = SearchCond.getLeaf(relationshipCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
 
         fiql = new AnyObjectFiqlSearchConditionBuilder("PRINTER").inRelationshipTypes("neighborhood").query();
         assertEquals(
@@ -182,9 +184,9 @@ public class SearchCondConverterTest {
 
         MembershipCond groupCond = new MembershipCond();
         groupCond.setGroup("e7ff94e8-19c9-4f0a-b8b7-28327edbf6ed");
-        SearchCond simpleCond = SearchCond.getLeafCond(groupCond);
+        SearchCond leaf = SearchCond.getLeaf(groupCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -194,9 +196,9 @@ public class SearchCondConverterTest {
 
         RoleCond roleCond = new RoleCond();
         roleCond.setRole("User reviewer");
-        SearchCond simpleCond = SearchCond.getLeafCond(roleCond);
+        SearchCond leaf = SearchCond.getLeaf(roleCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -206,9 +208,9 @@ public class SearchCondConverterTest {
 
         PrivilegeCond privilegeCond = new PrivilegeCond();
         privilegeCond.setPrivilege("postMighty");
-        SearchCond simpleCond = SearchCond.getLeafCond(privilegeCond);
+        SearchCond leaf = SearchCond.getLeaf(privilegeCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -219,9 +221,9 @@ public class SearchCondConverterTest {
 
         DynRealmCond dynRealmCond = new DynRealmCond();
         dynRealmCond.setDynRealm(dynRealm);
-        SearchCond simpleCond = SearchCond.getLeafCond(dynRealmCond);
+        SearchCond leaf = SearchCond.getLeaf(dynRealmCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -231,9 +233,9 @@ public class SearchCondConverterTest {
 
         ResourceCond resCond = new ResourceCond();
         resCond.setResourceKey("resource-ldap");
-        SearchCond simpleCond = SearchCond.getLeafCond(resCond);
+        SearchCond leaf = SearchCond.getLeaf(resCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -243,9 +245,9 @@ public class SearchCondConverterTest {
 
         AssignableCond assignableCond = new AssignableCond();
         assignableCond.setRealmFullPath("/even/two");
-        SearchCond simpleCond = SearchCond.getLeafCond(assignableCond);
+        SearchCond leaf = SearchCond.getLeaf(assignableCond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql, "/even/two"));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql, "/even/two"));
     }
 
     @Test
@@ -255,9 +257,9 @@ public class SearchCondConverterTest {
 
         AnyTypeCond acond = new AnyTypeCond();
         acond.setAnyTypeKey("PRINTER");
-        SearchCond simpleCond = SearchCond.getLeafCond(acond);
+        SearchCond leaf = SearchCond.getLeaf(acond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -267,9 +269,9 @@ public class SearchCondConverterTest {
 
         MemberCond mcond = new MemberCond();
         mcond.setMember("rossini");
-        SearchCond simpleCond = SearchCond.getLeafCond(mcond);
+        SearchCond leaf = SearchCond.getLeaf(mcond);
 
-        assertEquals(simpleCond, SearchCondConverter.convert(fiql));
+        assertEquals(leaf, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -278,17 +280,17 @@ public class SearchCondConverterTest {
                 is("fullname").equalTo("*o*").and("fullname").equalTo("*i*").query();
         assertEquals("fullname==*o*;fullname==*i*", fiql);
 
-        AttributeCond fullnameLeafCond1 = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond1 = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond1.setSchema("fullname");
         fullnameLeafCond1.setExpression("%o%");
-        AttributeCond fullnameLeafCond2 = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond2 = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond2.setSchema("fullname");
         fullnameLeafCond2.setExpression("%i%");
-        SearchCond andCond = SearchCond.getAndCond(
-                SearchCond.getLeafCond(fullnameLeafCond1),
-                SearchCond.getLeafCond(fullnameLeafCond2));
+        SearchCond andCond = SearchCond.getAnd(
+                SearchCond.getLeaf(fullnameLeafCond1),
+                SearchCond.getLeaf(fullnameLeafCond2));
 
-        assertEquals(andCond, SearchCondConverter.convert(fiql));
+        assertEquals(andCond, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
@@ -300,33 +302,32 @@ public class SearchCondConverterTest {
                 is("fullname").equalTo("*o*").or("fullname").equalTo("*i*").or("fullname").equalTo("*ini").query();
         assertEquals("fullname==*o*,fullname==*i*,fullname==*ini", fiql);
 
-        AttributeCond fullnameLeafCond1 = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond1 = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond1.setSchema("fullname");
         fullnameLeafCond1.setExpression("%o%");
-        AttributeCond fullnameLeafCond2 = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond2 = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond2.setSchema("fullname");
         fullnameLeafCond2.setExpression("%i%");
-        AttributeCond fullnameLeafCond3 = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond3 = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond3.setSchema("fullname");
         fullnameLeafCond3.setExpression("%ini");
-        SearchCond orCond = SearchCond.getOrCond(
-                SearchCond.getLeafCond(fullnameLeafCond1),
-                SearchCond.getOrCond(
-                        SearchCond.getLeafCond(fullnameLeafCond2),
-                        SearchCond.getLeafCond(fullnameLeafCond3)));
+        SearchCond orCond = SearchCond.getOr(SearchCond.getLeaf(fullnameLeafCond1),
+                SearchCond.getOr(
+                        SearchCond.getLeaf(fullnameLeafCond2),
+                        SearchCond.getLeaf(fullnameLeafCond3)));
 
-        assertEquals(orCond, SearchCondConverter.convert(fiql));
+        assertEquals(orCond, SearchCondConverter.convert(VISITOR, fiql));
     }
 
     @Test
     public void issueSYNCOPE1223() {
         String fiql = new UserFiqlSearchConditionBuilder().is("ctype").equalTo("ou=sample%252Co=isp").query();
 
-        AttributeCond cond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond cond = new AttrCond(AttrCond.Type.EQ);
         cond.setSchema("ctype");
         cond.setExpression("ou=sample,o=isp");
 
-        assertEquals(SearchCond.getLeafCond(cond), SearchCondConverter.convert(fiql));
+        assertEquals(SearchCond.getLeaf(cond), SearchCondConverter.convert(VISITOR, fiql));
     }
 
 }
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnySearchDAO.java
index 02f516f..92e4e3e 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractJPAJSONAnySearchDAO.java
@@ -19,7 +19,7 @@
 package org.apache.syncope.core.persistence.jpa.dao;
 
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 
 abstract class AbstractJPAJSONAnySearchDAO extends JPAAnySearchDAO {
 
@@ -28,7 +28,7 @@ abstract class AbstractJPAJSONAnySearchDAO extends JPAAnySearchDAO {
         return new SearchSupport(kind);
     }
 
-    protected void appendOp(final StringBuilder query, final AttributeCond.Type condType, final boolean not) {
+    protected void appendOp(final StringBuilder query, final AttrCond.Type condType, final boolean not) {
         switch (condType) {
             case LIKE:
             case ILIKE:
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONPlainSchemaDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONPlainSchemaDAO.java
index 44aea82..c588549 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONPlainSchemaDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAJSONPlainSchemaDAO.java
@@ -33,7 +33,7 @@ public class JPAJSONPlainSchemaDAO extends JPAPlainSchemaDAO {
     @Override
     public <T extends PlainAttr<?>> boolean hasAttrs(final PlainSchema schema, final Class<T> plainAttrTable) {
         // not possible
-        return false;
+        return true;
     }
 
     @Override
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
index b8d6a98..bc843fe 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/MyJPAJSONAnySearchDAO.java
@@ -24,7 +24,7 @@ import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
@@ -143,7 +143,7 @@ public class MyJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
             final StringBuilder query,
             final PlainAttrValue attrValue,
             final PlainSchema schema,
-            final AttributeCond cond,
+            final AttrCond cond,
             final boolean not,
             final List<Object> parameters,
             final SearchSupport svs) {
@@ -151,14 +151,14 @@ public class MyJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
         // This first branch is required for handling with not conditions given on multivalue fields (SYNCOPE-1419)
         if (not && schema.isMultivalue()
                 && !(cond instanceof AnyCond)
-                && cond.getType() != AttributeCond.Type.ISNULL && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+                && cond.getType() != AttrCond.Type.ISNULL && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
             query.append("id NOT IN (SELECT DISTINCT any_id FROM ");
             query.append(svs.field().name).append(" WHERE ");
             fillAttrQuery(anyUtils, query, attrValue, schema, cond, false, parameters, svs);
             query.append(')');
         } else {
-            if (!not && cond.getType() == AttributeCond.Type.EQ) {
+            if (!not && cond.getType() == AttrCond.Type.EQ) {
                 PlainAttr<?> container = anyUtils.newPlainAttr();
                 container.setSchema(schema);
                 if (attrValue instanceof PlainAttrUniqueValue) {
@@ -173,7 +173,7 @@ public class MyJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
             } else {
                 String key = key(schema.getType());
                 boolean lower = (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum)
-                        && (cond.getType() == AttributeCond.Type.IEQ || cond.getType() == AttributeCond.Type.ILIKE);
+                        && (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
 
                 query.append("plainSchema = ?").append(setParameter(parameters, cond.getSchema())).
                         append(" AND ").
@@ -194,7 +194,7 @@ public class MyJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
 
     @Override
     protected String getQuery(
-            final AttributeCond cond,
+            final AttrCond cond,
             final boolean not,
             final List<Object> parameters,
             final SearchSupport svs) {
@@ -208,10 +208,10 @@ public class MyJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
 
         // normalize NULL / NOT NULL checks
         if (not) {
-            if (cond.getType() == AttributeCond.Type.ISNULL) {
-                cond.setType(AttributeCond.Type.ISNOTNULL);
-            } else if (cond.getType() == AttributeCond.Type.ISNOTNULL) {
-                cond.setType(AttributeCond.Type.ISNULL);
+            if (cond.getType() == AttrCond.Type.ISNULL) {
+                cond.setType(AttrCond.Type.ISNOTNULL);
+            } else if (cond.getType() == AttrCond.Type.ISNOTNULL) {
+                cond.setType(AttrCond.Type.ISNULL);
             }
         }
 
diff --git a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
index b6d5b26..972a7af 100644
--- a/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
+++ b/core/persistence-jpa-json/src/main/java/org/apache/syncope/core/persistence/jpa/dao/PGJPAJSONAnySearchDAO.java
@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.entity.AnyUtils;
 import org.apache.syncope.core.persistence.api.entity.PlainAttr;
@@ -112,7 +112,7 @@ public class PGJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
             final StringBuilder query,
             final PlainAttrValue attrValue,
             final PlainSchema schema,
-            final AttributeCond cond,
+            final AttrCond cond,
             final boolean not,
             final List<Object> parameters,
             final SearchSupport svs) {
@@ -120,14 +120,14 @@ public class PGJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
         // This first branch is required for handling with not conditions given on multivalue fields (SYNCOPE-1419)
         if (not && schema.isMultivalue()
                 && !(cond instanceof AnyCond)
-                && cond.getType() != AttributeCond.Type.ISNULL && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+                && cond.getType() != AttrCond.Type.ISNULL && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
             query.append("id NOT IN (SELECT DISTINCT any_id FROM ");
             query.append(svs.field().name).append(" WHERE ");
             fillAttrQuery(anyUtils, query, attrValue, schema, cond, false, parameters, svs);
             query.append(')');
         } else {
-            if (!not && cond.getType() == AttributeCond.Type.EQ) {
+            if (!not && cond.getType() == AttrCond.Type.EQ) {
                 PlainAttr<?> container = anyUtils.newPlainAttr();
                 container.setSchema(schema);
                 if (attrValue instanceof PlainAttrUniqueValue) {
@@ -142,7 +142,7 @@ public class PGJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
             } else {
                 String key = key(schema.getType());
                 boolean lower = (schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum)
-                        && (cond.getType() == AttributeCond.Type.IEQ || cond.getType() == AttributeCond.Type.ILIKE);
+                        && (cond.getType() == AttrCond.Type.IEQ || cond.getType() == AttrCond.Type.ILIKE);
 
                 query.append("attrs ->> 'schema' = ?").append(setParameter(parameters, cond.getSchema())).
                         append(" AND ").
@@ -171,7 +171,7 @@ public class PGJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
 
     @Override
     protected String getQuery(
-            final AttributeCond cond,
+            final AttrCond cond,
             final boolean not,
             final List<Object> parameters,
             final SearchSupport svs) {
@@ -185,10 +185,10 @@ public class PGJPAJSONAnySearchDAO extends AbstractJPAJSONAnySearchDAO {
 
         // normalize NULL / NOT NULL checks
         if (not) {
-            if (cond.getType() == AttributeCond.Type.ISNULL) {
-                cond.setType(AttributeCond.Type.ISNOTNULL);
-            } else if (cond.getType() == AttributeCond.Type.ISNOTNULL) {
-                cond.setType(AttributeCond.Type.ISNULL);
+            if (cond.getType() == AttrCond.Type.ISNULL) {
+                cond.setType(AttrCond.Type.ISNOTNULL);
+            } else if (cond.getType() == AttrCond.Type.ISNOTNULL) {
+                cond.setType(AttrCond.Type.ISNULL);
             }
         }
 
diff --git a/core/persistence-jpa-json/src/main/resources/myjson/persistence.properties b/core/persistence-jpa-json/src/main/resources/myjson/persistence.properties
index a80d72e..d4b0495 100644
--- a/core/persistence-jpa-json/src/main/resources/myjson/persistence.properties
+++ b/core/persistence-jpa-json/src/main/resources/myjson/persistence.properties
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainSchemaDA
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.MyJPAJSONAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONAnyObjectDAO
diff --git a/core/persistence-jpa-json/src/main/resources/pgjsonb/persistence.properties b/core/persistence-jpa-json/src/main/resources/pgjsonb/persistence.properties
index 0912595..89f9e0d 100644
--- a/core/persistence-jpa-json/src/main/resources/pgjsonb/persistence.properties
+++ b/core/persistence-jpa-json/src/main/resources/pgjsonb/persistence.properties
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainSchemaDA
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.PGJPAJSONAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONAnyObjectDAO
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index 5a11d41..1a6e0f2 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -2339,62 +2339,28 @@ $$ }&#10;
 
   <SecurityQuestion id="887028ea-66fc-41e7-b397-620d7ea6dfbb" content="What's your mother's maiden name?"/>
 
-  <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
+  <GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"
+                predicates="[{&quot;cond&quot;:null,&quot;factory&quot;:&quot;METHOD&quot;,&quot;args&quot;:&quot;GET&quot;}]"/>
 
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
 
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
 
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
@@ -2409,6 +2375,13 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
 
-  <GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"
-                predicates="[{&quot;cond&quot;:null,&quot;factory&quot;:&quot;METHOD&quot;,&quot;args&quot;:&quot;GET&quot;}]"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
 </dataset>
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 4ce8ec6..91bd97a 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
@@ -32,6 +32,7 @@ import org.apache.syncope.core.persistence.api.dao.PlainAttrValueDAO;
 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.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.persistence.jpa.spring.CommonEntityManagerFactoryConf;
 import org.apache.syncope.core.persistence.jpa.spring.DomainTransactionInterceptorInjector;
 import org.apache.syncope.core.persistence.jpa.spring.MultiJarAwarePersistenceUnitPostProcessor;
@@ -145,6 +146,15 @@ public class PersistenceContext implements EnvironmentAware {
         return (AnySearchDAO) Class.forName(env.getProperty("any.search.dao")).getConstructor().newInstance();
     }
 
+    @ConditionalOnMissingBean(name = "anySearchVisitor")
+    @Bean
+    public SearchCondVisitor anySearchVisitor()
+            throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException,
+            IllegalArgumentException, InvocationTargetException {
+
+        return (SearchCondVisitor) Class.forName(env.getProperty("any.search.visitor")).getConstructor().newInstance();
+    }
+
     @ConditionalOnMissingBean(name = "userDAO")
     @Bean
     public UserDAO userDAO()
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
index 496b609..05d6a34 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnyDAO.java
@@ -44,7 +44,7 @@ import org.apache.syncope.core.persistence.api.dao.DynRealmDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 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.AttributeCond;
+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.AnyTypeClass;
@@ -456,9 +456,9 @@ public abstract class AbstractAnyDAO<A extends Any<?>> extends AbstractDAO<A> im
 
     @Override
     public SearchCond getAllMatchingCond() {
-        AnyCond idCond = new AnyCond(AttributeCond.Type.ISNOTNULL);
+        AnyCond idCond = new AnyCond(AttrCond.Type.ISNOTNULL);
         idCond.setSchema("id");
-        return SearchCond.getLeafCond(idCond);
+        return SearchCond.getLeaf(idCond);
     }
 
     @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
index 81624a5..28cd299 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java
@@ -42,7 +42,7 @@ 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.AssignableCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
@@ -98,11 +98,11 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
         List<SearchCond> effectiveConds = dynRealmKeys.stream().map(dynRealmKey -> {
             DynRealmCond dynRealmCond = new DynRealmCond();
             dynRealmCond.setDynRealm(dynRealmKey);
-            return SearchCond.getLeafCond(dynRealmCond);
+            return SearchCond.getLeaf(dynRealmCond);
         }).collect(Collectors.toList());
         effectiveConds.add(cond);
 
-        return SearchCond.getAndCond(effectiveConds);
+        return SearchCond.getAnd(effectiveConds);
     }
 
     protected abstract int doCount(Set<String> adminRealms, SearchCond cond, AnyTypeKind kind);
@@ -143,7 +143,7 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
             List<OrderByClause> orderBy,
             AnyTypeKind kind);
 
-    protected Pair<PlainSchema, PlainAttrValue> check(final AttributeCond cond, final AnyTypeKind kind) {
+    protected Pair<PlainSchema, PlainAttrValue> check(final AttrCond cond, final AnyTypeKind kind) {
         AnyUtils anyUtils = anyUtilsFactory.getInstance(kind);
 
         PlainSchema schema = schemaDAO.find(cond.getSchema());
@@ -156,10 +156,10 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
                 ? anyUtils.newPlainAttrUniqueValue()
                 : anyUtils.newPlainAttrValue();
         try {
-            if (cond.getType() != AttributeCond.Type.LIKE
-                    && cond.getType() != AttributeCond.Type.ILIKE
-                    && cond.getType() != AttributeCond.Type.ISNULL
-                    && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+            if (cond.getType() != AttrCond.Type.LIKE
+                    && cond.getType() != AttrCond.Type.ILIKE
+                    && cond.getType() != AttrCond.Type.ISNULL
+                    && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
                 ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
             }
@@ -219,10 +219,10 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implement
         }
 
         PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
-        if (computed.getType() != AttributeCond.Type.LIKE
-                && computed.getType() != AttributeCond.Type.ILIKE
-                && computed.getType() != AttributeCond.Type.ISNULL
-                && computed.getType() != AttributeCond.Type.ISNOTNULL) {
+        if (computed.getType() != AttrCond.Type.LIKE
+                && computed.getType() != AttrCond.Type.ILIKE
+                && computed.getType() != AttrCond.Type.ISNULL
+                && computed.getType() != AttrCond.Type.ISNOTNULL) {
 
             try {
                 ((JPAPlainSchema) schema).validator().validate(computed.getExpression(), attrValue);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPullCorrelationRule.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPullCorrelationRule.java
index 05f6338..23caa5a 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPullCorrelationRule.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/DefaultPullCorrelationRule.java
@@ -27,7 +27,7 @@ import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.policy.DefaultPullCorrelationRuleConf;
 import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+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.resource.Item;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
@@ -68,33 +68,33 @@ public class DefaultPullCorrelationRule implements PullCorrelationRule {
                         "Connector object does not contains the attributes to perform the search: " + schema);
             }
 
-            AttributeCond.Type type;
+            AttrCond.Type type;
             String expression = null;
 
             if (attr.getValue() == null || attr.getValue().isEmpty()
                     || (attr.getValue().size() == 1 && attr.getValue().get(0) == null)) {
 
-                type = AttributeCond.Type.ISNULL;
+                type = AttrCond.Type.ISNULL;
             } else {
-                type = AttributeCond.Type.EQ;
+                type = AttrCond.Type.EQ;
                 expression = attr.getValue().size() > 1
                         ? attr.getValue().toString()
                         : attr.getValue().get(0).toString();
             }
 
-            AttributeCond cond = "key".equalsIgnoreCase(schema)
+            AttrCond cond = "key".equalsIgnoreCase(schema)
                     || "username".equalsIgnoreCase(schema) || "name".equalsIgnoreCase(schema)
                     ? new AnyCond()
-                    : new AttributeCond();
+                    : new AttrCond();
             cond.setSchema(schema);
             cond.setType(type);
             cond.setExpression(expression);
 
-            searchConds.add(SearchCond.getLeafCond(cond));
+            searchConds.add(SearchCond.getLeaf(cond));
         });
 
         return conf.isOrSchemas()
-                ? SearchCond.getOrCond(searchConds)
-                : SearchCond.getAndCond(searchConds);
+                ? SearchCond.getOr(searchConds)
+                : SearchCond.getAnd(searchConds);
     }
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
index 1f37e4c..0e81893 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnyMatchDAO.java
@@ -30,6 +30,7 @@ import javax.persistence.Entity;
 import javax.validation.ValidationException;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
+import org.apache.commons.lang3.BooleanUtils;
 import org.apache.commons.lang3.ClassUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
@@ -41,7 +42,7 @@ import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
 import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond;
 import org.apache.syncope.core.persistence.api.dao.search.AssignableCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
@@ -104,36 +105,88 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
         switch (cond.getType()) {
             case LEAF:
             case NOT_LEAF:
-                if (cond.getAnyTypeCond() != null && AnyTypeKind.ANY_OBJECT == any.getType().getKind()) {
-                    return matches(any, cond.getAnyTypeCond(), not);
-                } else if (cond.getRelationshipTypeCond() != null && any instanceof GroupableRelatable) {
-                    return matches((GroupableRelatable) any, cond.getRelationshipTypeCond(), not);
-                } else if (cond.getRelationshipCond() != null && any instanceof GroupableRelatable) {
-                    return matches((GroupableRelatable) any, cond.getRelationshipCond(), not);
-                } else if (cond.getMembershipCond() != null && any instanceof GroupableRelatable) {
-                    return matches((GroupableRelatable) any, cond.getMembershipCond(), not);
-                } else if (cond.getAssignableCond() != null) {
-                    return matches(any, cond.getAssignableCond(), not);
-                } else if (cond.getRoleCond() != null && any instanceof User) {
-                    return matches((User) any, cond.getRoleCond(), not);
-                } else if (cond.getDynRealmCond() != null) {
-                    return matches(any, cond.getDynRealmCond(), not);
-                } else if (cond.getMemberCond() != null && any instanceof Group) {
-                    return matches((Group) any, cond.getMemberCond(), not);
-                } else if (cond.getResourceCond() != null) {
-                    return matches(any, cond.getResourceCond(), not);
-                } else if (cond.getAttributeCond() != null) {
-                    return matches(any, cond.getAttributeCond(), not);
-                } else if (cond.getAnyCond() != null) {
-                    return matches(any, cond.getAnyCond(), not);
+                Boolean match = cond.getLeaf(AnyTypeCond.class).
+                        filter(leaf -> AnyTypeKind.ANY_OBJECT == any.getType().getKind()).
+                        map(leaf -> matches(any, leaf, not)).
+                        orElse(null);
+
+                if (match == null) {
+                    match = cond.getLeaf(RelationshipTypeCond.class).
+                            filter(leaf -> any instanceof GroupableRelatable).
+                            map(leaf -> matches((GroupableRelatable) any, leaf, not)).
+                            orElse(null);
                 }
-                break;
+
+                if (match == null) {
+                    match = cond.getLeaf(RelationshipCond.class).
+                            filter(leaf -> any instanceof GroupableRelatable).
+                            map(leaf -> matches((GroupableRelatable) any, leaf, not)).
+                            orElse(null);
+                }
+
+                if (match == null) {
+                    match = cond.getLeaf(MembershipCond.class).
+                            filter(leaf -> any instanceof GroupableRelatable).
+                            map(leaf -> matches((GroupableRelatable) any, leaf, not)).
+                            orElse(null);
+                }
+
+                if (match == null) {
+                    match = cond.getLeaf(AssignableCond.class).
+                            map(leaf -> matches(any, leaf, not)).
+                            orElse(null);
+                }
+
+                if (match == null) {
+                    match = cond.getLeaf(RoleCond.class).
+                            filter(leaf -> any instanceof User).
+                            map(leaf -> matches((User) any, leaf, not)).
+                            orElse(null);
+                }
+
+                if (match == null) {
+                    match = cond.getLeaf(DynRealmCond.class).
+                            map(leaf -> matches(any, leaf, not)).
+                            orElse(null);
+                }
+
+                if (match == null) {
+                    match = cond.getLeaf(MemberCond.class).
+                            filter(leaf -> any instanceof Group).
+                            map(leaf -> matches((Group) any, leaf, not)).
+                            orElse(null);
+                }
+
+                if (match == null) {
+                    match = cond.getLeaf(ResourceCond.class).
+                            map(leaf -> matches(any, leaf, not)).
+                            orElse(null);
+                }
+
+                if (match == null) {
+                    Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
+                    if (anyCond.isPresent()) {
+                        match = matches(any, anyCond.get(), not);
+                    } else {
+                        match = cond.getLeaf(AttrCond.class).
+                                map(leaf -> matches(any, leaf, not)).
+                                orElse(null);
+                    }
+                }
+
+                if (match == null) {
+                    match = cond.getLeaf(AttrCond.class).
+                            map(leaf -> matches(any, leaf, not)).
+                            orElse(null);
+                }
+
+                return BooleanUtils.toBoolean(match);
 
             case AND:
-                return matches(any, cond.getLeftSearchCond()) && matches(any, cond.getRightSearchCond());
+                return matches(any, cond.getLeft()) && matches(any, cond.getRight());
 
             case OR:
-                return matches(any, cond.getLeftSearchCond()) || matches(any, cond.getRightSearchCond());
+                return matches(any, cond.getLeft()) || matches(any, cond.getRight());
 
             default:
         }
@@ -228,10 +281,10 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
     private static boolean matches(
-        final List<? extends PlainAttrValue> anyAttrValues,
-        final PlainAttrValue attrValue,
-        final PlainSchema schema,
-        final AttributeCond cond) {
+            final List<? extends PlainAttrValue> anyAttrValues,
+            final PlainAttrValue attrValue,
+            final PlainSchema schema,
+            final AttrCond cond) {
 
         return anyAttrValues.stream().anyMatch(item -> {
             switch (cond.getType()) {
@@ -262,7 +315,7 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
                                 output.append(c);
                             }
                         }
-                        return (cond.getType() == AttributeCond.Type.LIKE
+                        return (cond.getType() == AttrCond.Type.LIKE
                                 ? Pattern.compile(output.toString())
                                 : Pattern.compile(output.toString(), Pattern.CASE_INSENSITIVE)).
                                 matcher(item.getStringValue()).matches();
@@ -289,7 +342,7 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
         });
     }
 
-    private boolean matches(final Any<?> any, final AttributeCond cond, final boolean not) {
+    private boolean matches(final Any<?> any, final AttrCond cond, final boolean not) {
         PlainSchema schema = plainSchemaDAO.find(cond.getSchema());
         if (schema == null) {
             LOG.warn("Ignoring invalid schema '{}'", cond.getSchema());
@@ -312,10 +365,10 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
             default:
                 PlainAttrValue attrValue = anyUtilsFactory.getInstance(any).newPlainAttrValue();
                 try {
-                    if (cond.getType() != AttributeCond.Type.LIKE
-                            && cond.getType() != AttributeCond.Type.ILIKE
-                            && cond.getType() != AttributeCond.Type.ISNULL
-                            && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+                    if (cond.getType() != AttrCond.Type.LIKE
+                            && cond.getType() != AttrCond.Type.ILIKE
+                            && cond.getType() != AttrCond.Type.ISNULL
+                            && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
                         ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
                     }
@@ -403,10 +456,10 @@ public class JPAAnyMatchDAO extends AbstractDAO<Any<?>> implements AnyMatchDAO {
                 AnyUtils anyUtils = anyUtilsFactory.getInstance(any);
 
                 PlainAttrValue attrValue = anyUtils.newPlainAttrValue();
-                if (cond.getType() != AttributeCond.Type.LIKE
-                        && cond.getType() != AttributeCond.Type.ILIKE
-                        && cond.getType() != AttributeCond.Type.ISNULL
-                        && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+                if (cond.getType() != AttrCond.Type.LIKE
+                        && cond.getType() != AttrCond.Type.ILIKE
+                        && cond.getType() != AttrCond.Type.ISNULL
+                        && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
                     try {
                         ((JPAPlainSchema) schema).validator().validate(cond.getExpression(), attrValue);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
index dfe23c8..3bf81da 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.persistence.Query;
@@ -34,10 +35,10 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 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.AssignableCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
@@ -439,6 +440,16 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
         return obs;
     }
 
+    protected void getQueryForCustomConds(
+            final SearchCond cond,
+            final List<Object> parameters,
+            final SearchSupport svs,
+            final boolean not,
+            final StringBuilder query) {
+
+        // do nothing by default, leave it open for subclasses
+    }
+
     private Pair<StringBuilder, Set<String>> getQuery(
             final SearchCond cond, final List<Object> parameters, final SearchSupport svs) {
 
@@ -450,49 +461,66 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
         switch (cond.getType()) {
             case LEAF:
             case NOT_LEAF:
-                if (cond.getAnyTypeCond() != null && AnyTypeKind.ANY_OBJECT == svs.anyTypeKind) {
-                    query.append(getQuery(cond.getAnyTypeCond(), not, parameters, svs));
-                } else if (cond.getRelationshipTypeCond() != null
-                        && (AnyTypeKind.USER == svs.anyTypeKind || AnyTypeKind.ANY_OBJECT == svs.anyTypeKind)) {
-
-                    query.append(getQuery(cond.getRelationshipTypeCond(), not, parameters, svs));
-                } else if (cond.getRelationshipCond() != null
-                        && (AnyTypeKind.USER == svs.anyTypeKind || AnyTypeKind.ANY_OBJECT == svs.anyTypeKind)) {
-
-                    query.append(getQuery(cond.getRelationshipCond(), not, parameters, svs));
-                } else if (cond.getMembershipCond() != null
-                        && (AnyTypeKind.USER == svs.anyTypeKind || AnyTypeKind.ANY_OBJECT == svs.anyTypeKind)) {
-
-                    query.append(getQuery(cond.getMembershipCond(), not, parameters, svs));
-                } else if (cond.getAssignableCond() != null) {
-                    query.append(getQuery(cond.getAssignableCond(), parameters, svs));
-                } else if (cond.getRoleCond() != null && AnyTypeKind.USER == svs.anyTypeKind) {
-                    query.append(getQuery(cond.getRoleCond(), not, parameters, svs));
-                } else if (cond.getPrivilegeCond() != null && AnyTypeKind.USER == svs.anyTypeKind) {
-                    query.append(getQuery(cond.getPrivilegeCond(), not, parameters, svs));
-                } else if (cond.getDynRealmCond() != null) {
-                    query.append(getQuery(cond.getDynRealmCond(), not, parameters, svs));
-                } else if (cond.getMemberCond() != null && AnyTypeKind.GROUP == svs.anyTypeKind) {
-                    query.append(getQuery(cond.getMemberCond(), not, parameters, svs));
-                } else if (cond.getResourceCond() != null) {
-                    query.append(getQuery(cond.getResourceCond(), not, parameters, svs));
-                } else if (cond.getAttributeCond() != null) {
-                    query.append(getQuery(cond.getAttributeCond(), not, parameters, svs));
-                    try {
-                        involvedPlainAttrs.add(check(cond.getAttributeCond(), svs.anyTypeKind).getLeft().getKey());
-                    } catch (IllegalArgumentException e) {
-                        // ignore
-                    }
-                } else if (cond.getAnyCond() != null) {
-                    query.append(getQuery(cond.getAnyCond(), not, parameters, svs));
+                cond.getLeaf(AnyTypeCond.class).
+                        filter(leaf -> AnyTypeKind.ANY_OBJECT == svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(RelationshipTypeCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP != svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(RelationshipCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP != svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(MembershipCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP != svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(MemberCond.class).
+                        filter(leaf -> AnyTypeKind.GROUP == svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(AssignableCond.class).
+                        ifPresent(leaf -> query.append(getQuery(leaf, parameters, svs)));
+
+                cond.getLeaf(RoleCond.class).
+                        filter(leaf -> AnyTypeKind.USER == svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(PrivilegeCond.class).
+                        filter(leaf -> AnyTypeKind.USER == svs.anyTypeKind).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(DynRealmCond.class).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                cond.getLeaf(ResourceCond.class).
+                        ifPresent(leaf -> query.append(getQuery(leaf, not, parameters, svs)));
+
+                Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
+                if (anyCond.isPresent()) {
+                    query.append(getQuery(anyCond.get(), not, parameters, svs));
+                } else {
+                    cond.getLeaf(AttrCond.class).ifPresent(leaf -> {
+                        query.append(getQuery(leaf, not, parameters, svs));
+                        try {
+                            involvedPlainAttrs.add(check(leaf, svs.anyTypeKind).getLeft().getKey());
+                        } catch (IllegalArgumentException e) {
+                            // ignore
+                        }
+                    });
                 }
+
+                // allow for additional search conditions
+                getQueryForCustomConds(cond, parameters, svs, not, query);
                 break;
 
             case AND:
-                Pair<StringBuilder, Set<String>> leftAndInfo = getQuery(cond.getLeftSearchCond(), parameters, svs);
+                Pair<StringBuilder, Set<String>> leftAndInfo = getQuery(cond.getLeft(), parameters, svs);
                 involvedPlainAttrs.addAll(leftAndInfo.getRight());
 
-                Pair<StringBuilder, Set<String>> rigthAndInfo = getQuery(cond.getRightSearchCond(), parameters, svs);
+                Pair<StringBuilder, Set<String>> rigthAndInfo = getQuery(cond.getRight(), parameters, svs);
                 involvedPlainAttrs.addAll(rigthAndInfo.getRight());
 
                 String andSubQuery = leftAndInfo.getKey().toString();
@@ -505,10 +533,10 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
                 break;
 
             case OR:
-                Pair<StringBuilder, Set<String>> leftOrInfo = getQuery(cond.getLeftSearchCond(), parameters, svs);
+                Pair<StringBuilder, Set<String>> leftOrInfo = getQuery(cond.getLeft(), parameters, svs);
                 involvedPlainAttrs.addAll(leftOrInfo.getRight());
 
-                Pair<StringBuilder, Set<String>> rigthOrInfo = getQuery(cond.getRightSearchCond(), parameters, svs);
+                Pair<StringBuilder, Set<String>> rigthOrInfo = getQuery(cond.getRight(), parameters, svs);
                 involvedPlainAttrs.addAll(rigthOrInfo.getRight());
 
                 String orSubQuery = leftOrInfo.getKey().toString();
@@ -527,10 +555,10 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected static String getQuery(
-        final AnyTypeCond cond,
-        final boolean not,
-        final List<Object> parameters,
-        final SearchSupport svs) {
+            final AnyTypeCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
 
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
                 append(svs.field().name).append(" WHERE type_id");
@@ -547,10 +575,10 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected static String getQuery(
-        final RelationshipTypeCond cond,
-        final boolean not,
-        final List<Object> parameters,
-        final SearchSupport svs) {
+            final RelationshipTypeCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
 
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
                 append(svs.field().name).append(" WHERE ");
@@ -644,10 +672,10 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected static String getQuery(
-        final RoleCond cond,
-        final boolean not,
-        final List<Object> parameters,
-        final SearchSupport svs) {
+            final RoleCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
 
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
                 append(svs.field().name).append(" WHERE (");
@@ -678,10 +706,10 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected static String getQuery(
-        final PrivilegeCond cond,
-        final boolean not,
-        final List<Object> parameters,
-        final SearchSupport svs) {
+            final PrivilegeCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
 
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
                 append(svs.field().name).append(" WHERE (");
@@ -712,10 +740,10 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected static String getQuery(
-        final DynRealmCond cond,
-        final boolean not,
-        final List<Object> parameters,
-        final SearchSupport svs) {
+            final DynRealmCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
 
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
                 append(svs.field().name).append(" WHERE (");
@@ -735,10 +763,10 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected static String getQuery(
-        final ResourceCond cond,
-        final boolean not,
-        final List<Object> parameters,
-        final SearchSupport svs) {
+            final ResourceCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
 
         StringBuilder query = new StringBuilder("SELECT DISTINCT any_id FROM ").
                 append(svs.field().name).append(" WHERE ");
@@ -837,18 +865,18 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     private static void fillAttrQuery(
-        final StringBuilder query,
-        final PlainAttrValue attrValue,
-        final PlainSchema schema,
-        final AttributeCond cond,
-        final boolean not,
-        final List<Object> parameters,
-        final SearchSupport svs) {
+            final StringBuilder query,
+            final PlainAttrValue attrValue,
+            final PlainSchema schema,
+            final AttrCond cond,
+            final boolean not,
+            final List<Object> parameters,
+            final SearchSupport svs) {
 
         // This first branch is required for handling with not conditions given on multivalue fields (SYNCOPE-1419)
         if (not && schema.isMultivalue()
                 && !(cond instanceof AnyCond)
-                && cond.getType() != AttributeCond.Type.ISNULL && cond.getType() != AttributeCond.Type.ISNOTNULL) {
+                && cond.getType() != AttrCond.Type.ISNULL && cond.getType() != AttrCond.Type.ISNOTNULL) {
 
             query.append("any_id NOT IN (SELECT DISTINCT any_id FROM ");
             if (schema.isUniqueConstraint()) {
@@ -861,7 +889,7 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
             query.append(')');
         } else {
             // activate ignoreCase only for EQ and LIKE operators
-            boolean ignoreCase = AttributeCond.Type.ILIKE == cond.getType() || AttributeCond.Type.IEQ == cond.getType();
+            boolean ignoreCase = AttrCond.Type.ILIKE == cond.getType() || AttrCond.Type.IEQ == cond.getType();
 
             String column = (cond instanceof AnyCond) ? cond.getSchema() : key(schema.getType());
             if ((schema.getType() == AttrSchemaType.String || schema.getType() == AttrSchemaType.Enum) && ignoreCase) {
@@ -969,7 +997,7 @@ public class JPAAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     protected String getQuery(
-            final AttributeCond cond,
+            final AttrCond cond,
             final boolean not,
             final List<Object> parameters,
             final SearchSupport svs) {
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
index 21da9ce..837e043 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPADynRealmDAO.java
@@ -38,6 +38,7 @@ import org.apache.syncope.core.persistence.api.dao.AnyMatchDAO;
 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.UserDAO;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 
 @Repository
 public class JPADynRealmDAO extends AbstractDAO<DynRealm> implements DynRealmDAO {
@@ -62,6 +63,9 @@ public class JPADynRealmDAO extends AbstractDAO<DynRealm> implements DynRealmDAO
     @Autowired
     private AnyMatchDAO anyMatchDAO;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     @Override
     public DynRealm find(final String key) {
         return entityManager().find(JPADynRealm.class, key);
@@ -121,7 +125,7 @@ public class JPADynRealmDAO extends AbstractDAO<DynRealm> implements DynRealmDAO
         List<String> cleared = clearDynMembers(merged);
 
         merged.getDynMemberships().stream().map(memb -> searchDAO.search(
-                SearchCondConverter.convert(memb.getFIQLCond()), memb.getAnyType().getKind())).
+                SearchCondConverter.convert(searchCondVisitor, memb.getFIQLCond()), memb.getAnyType().getKind())).
                 forEach(matching -> matching.forEach(any -> {
 
             Query insert = entityManager().createNativeQuery("INSERT INTO " + DYNMEMB_TABLE + " VALUES(?, ?)");
@@ -154,7 +158,8 @@ public class JPADynRealmDAO extends AbstractDAO<DynRealm> implements DynRealmDAO
     @Override
     public void refreshDynMemberships(final Any<?> any) {
         findAll().forEach(dynRealm -> dynRealm.getDynMembership(any.getType()).ifPresent(memb -> {
-            boolean matches = anyMatchDAO.matches(any, SearchCondConverter.convert(memb.getFIQLCond()));
+            boolean matches = anyMatchDAO.matches(
+                    any, SearchCondConverter.convert(searchCondVisitor, memb.getFIQLCond()));
 
             Query find = entityManager().createNativeQuery(
                     "SELECT dynRealm_id FROM " + DYNMEMB_TABLE + " WHERE any_id=?");
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
index 7829c85..0e29990 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
@@ -56,6 +56,7 @@ import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
 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.search.SearchCondConverter;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
@@ -91,6 +92,9 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
     @Autowired
     private AnySearchDAO searchDAO;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     @Override
     protected AnyUtils init() {
         return anyUtilsFactory.getInstance(AnyTypeKind.GROUP);
@@ -232,12 +236,14 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> implements GroupDAO {
         return findAllKeys(JPAGroup.TABLE, page, itemsPerPage);
     }
 
-    private static SearchCond buildDynMembershipCond(final String baseCondFIQL, final Realm groupRealm) {
+    private SearchCond buildDynMembershipCond(final String baseCondFIQL, final Realm groupRealm) {
         AssignableCond cond = new AssignableCond();
         cond.setRealmFullPath(groupRealm.getFullPath());
         cond.setFromGroup(true);
 
-        return SearchCond.getAndCond(SearchCond.getLeafCond(cond), SearchCondConverter.convert(baseCondFIQL));
+        return SearchCond.getAnd(
+                SearchCond.getLeaf(cond),
+                SearchCondConverter.convert(searchCondVisitor, baseCondFIQL));
     }
 
     @Override
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
index 48f5474..27928cd 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARoleDAO.java
@@ -31,6 +31,7 @@ import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.persistence.jpa.entity.JPARole;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
 import org.apache.syncope.core.provisioning.api.event.AnyCreatedUpdatedEvent;
@@ -54,6 +55,9 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
     @Autowired
     private AnySearchDAO searchDAO;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     @Override
     public int count() {
         Query query = entityManager().createQuery(
@@ -103,7 +107,8 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
         clearDynMembers(merged);
         if (merged.getDynMembership() != null) {
             List<User> matching = searchDAO.search(
-                    SearchCondConverter.convert(merged.getDynMembership().getFIQLCond()), AnyTypeKind.USER);
+                    SearchCondConverter.convert(searchCondVisitor, merged.getDynMembership().getFIQLCond()),
+                    AnyTypeKind.USER);
 
             matching.forEach((user) -> {
                 Query insert = entityManager().createNativeQuery("INSERT INTO " + DYNMEMB_TABLE + " VALUES(?, ?)");
@@ -177,8 +182,8 @@ public class JPARoleDAO extends AbstractDAO<Role> implements RoleDAO {
         query.setParameter(1, user.getKey());
 
         findAll().stream().filter(role -> role.getDynMembership() != null).forEach(role -> {
-            boolean matches =
-                    anyMatchDAO.matches(user, SearchCondConverter.convert(role.getDynMembership().getFIQLCond()));
+            boolean matches = anyMatchDAO.matches(
+                    user, SearchCondConverter.convert(searchCondVisitor, role.getDynMembership().getFIQLCond()));
 
             Query find = entityManager().createNativeQuery(
                     "SELECT any_id FROM " + DYNMEMB_TABLE + " WHERE role_id=?");
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARole.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARole.java
index 2a0269a..1101039 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARole.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARole.java
@@ -89,7 +89,7 @@ public class JPARole extends AbstractProvidedKeyEntity implements Role {
     private JPADynRoleMembership dynMembership;
 
     @Lob
-    private String consoleLayoutInfo;
+    private String anyLayout;
 
     @ManyToMany(fetch = FetchType.EAGER)
     @JoinTable(joinColumns =
@@ -140,13 +140,13 @@ public class JPARole extends AbstractProvidedKeyEntity implements Role {
     }
 
     @Override
-    public String getConsoleLayoutInfo() {
-        return consoleLayoutInfo;
+    public String getAnyLayout() {
+        return anyLayout;
     }
 
     @Override
-    public void setConsoleLayoutInfo(final String consoleLayoutInfo) {
-        this.consoleLayoutInfo = consoleLayoutInfo;
+    public void setAnyLayout(final String anyLayout) {
+        this.anyLayout = anyLayout;
     }
 
     @Override
diff --git a/core/persistence-jpa/src/main/resources/persistence.properties b/core/persistence-jpa/src/main/resources/persistence.properties
index 448aaef..ac6d8a8 100644
--- a/core/persistence-jpa/src/main/resources/persistence.properties
+++ b/core/persistence-jpa/src/main/resources/persistence.properties
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainSchemaDAO
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.JPAAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAAnyObjectDAO
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnyMatchTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnyMatchTest.java
index 512538c..8c60f6e 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnyMatchTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AnyMatchTest.java
@@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 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.UserDAO;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 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.RelationshipTypeCond;
@@ -64,10 +64,10 @@ public class AnyMatchTest extends AbstractTest {
 
         ResourceCond resourceCond = new ResourceCond();
         resourceCond.setResourceKey("resource-testdb2");
-        assertTrue(anyMatcher.matches(user, SearchCond.getLeafCond(resourceCond)));
+        assertTrue(anyMatcher.matches(user, SearchCond.getLeaf(resourceCond)));
 
         resourceCond.setResourceKey("ws-target-resource-delete");
-        assertFalse(anyMatcher.matches(user, SearchCond.getLeafCond(resourceCond)));
+        assertFalse(anyMatcher.matches(user, SearchCond.getLeaf(resourceCond)));
     }
 
     @Test
@@ -77,11 +77,11 @@ public class AnyMatchTest extends AbstractTest {
 
         RelationshipCond relationshipCond = new RelationshipCond();
         relationshipCond.setAnyObject("Canon MF 8030cn");
-        assertTrue(anyMatcher.matches(anyObject, SearchCond.getLeafCond(relationshipCond)));
+        assertTrue(anyMatcher.matches(anyObject, SearchCond.getLeaf(relationshipCond)));
 
         RelationshipTypeCond relationshipTypeCond = new RelationshipTypeCond();
         relationshipTypeCond.setRelationshipTypeKey("neighborhood");
-        assertTrue(anyMatcher.matches(anyObject, SearchCond.getLeafCond(relationshipTypeCond)));
+        assertTrue(anyMatcher.matches(anyObject, SearchCond.getLeaf(relationshipTypeCond)));
     }
 
     @Test
@@ -91,25 +91,25 @@ public class AnyMatchTest extends AbstractTest {
 
         MembershipCond groupCond = new MembershipCond();
         groupCond.setGroup("secretary");
-        assertFalse(anyMatcher.matches(user, SearchCond.getLeafCond(groupCond)));
+        assertFalse(anyMatcher.matches(user, SearchCond.getLeaf(groupCond)));
 
         groupCond.setGroup("root");
-        assertTrue(anyMatcher.matches(user, SearchCond.getLeafCond(groupCond)));
+        assertTrue(anyMatcher.matches(user, SearchCond.getLeaf(groupCond)));
 
         RoleCond roleCond = new RoleCond();
         roleCond.setRole("Other");
-        assertTrue(anyMatcher.matches(user, SearchCond.getLeafCond(roleCond)));
+        assertTrue(anyMatcher.matches(user, SearchCond.getLeaf(roleCond)));
 
         user = userDAO.find("c9b2dec2-00a7-4855-97c0-d854842b4b24");
         assertNotNull(user);
 
         RelationshipCond relationshipCond = new RelationshipCond();
         relationshipCond.setAnyObject("fc6dbc3a-6c07-4965-8781-921e7401a4a5");
-        assertTrue(anyMatcher.matches(user, SearchCond.getLeafCond(relationshipCond)));
+        assertTrue(anyMatcher.matches(user, SearchCond.getLeaf(relationshipCond)));
 
         RelationshipTypeCond relationshipTypeCond = new RelationshipTypeCond();
         relationshipTypeCond.setRelationshipTypeKey("neighborhood");
-        assertTrue(anyMatcher.matches(user, SearchCond.getLeafCond(relationshipTypeCond)));
+        assertTrue(anyMatcher.matches(user, SearchCond.getLeaf(relationshipTypeCond)));
     }
 
     @Test
@@ -120,12 +120,12 @@ public class AnyMatchTest extends AbstractTest {
         AnyCond anyCond = new AnyCond();
         anyCond.setSchema("name");
         anyCond.setExpression("root");
-        anyCond.setType(AttributeCond.Type.EQ);
-        assertTrue(anyMatcher.matches(group, SearchCond.getLeafCond(anyCond)));
+        anyCond.setType(AttrCond.Type.EQ);
+        assertTrue(anyMatcher.matches(group, SearchCond.getLeaf(anyCond)));
 
-        AttributeCond attrCond = new AttributeCond();
+        AttrCond attrCond = new AttrCond();
         attrCond.setSchema("show");
-        attrCond.setType(AttributeCond.Type.ISNOTNULL);
-        assertTrue(anyMatcher.matches(group, SearchCond.getLeafCond(attrCond)));
+        attrCond.setType(AttrCond.Type.ISNOTNULL);
+        assertTrue(anyMatcher.matches(group, SearchCond.getLeaf(attrCond)));
     }
 }
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 bfdf59f..733233f 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
@@ -34,7 +34,7 @@ import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 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.RealmDAO;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.ResourceCond;
@@ -77,23 +77,23 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void searchWithLikeCondition() {
-        AttributeCond fullnameLeafCond = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond.setSchema("fullname");
         fullnameLeafCond.setExpression("%o%");
 
         MembershipCond groupCond = new MembershipCond();
         groupCond.setGroup("root");
 
-        AttributeCond loginDateCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond loginDateCond = new AttrCond(AttrCond.Type.EQ);
         loginDateCond.setSchema("loginDate");
         loginDateCond.setExpression("2009-05-26");
 
-        SearchCond subCond = SearchCond.getAndCond(
-                SearchCond.getLeafCond(fullnameLeafCond), SearchCond.getLeafCond(groupCond));
+        SearchCond subCond = SearchCond.getAnd(
+                SearchCond.getLeaf(fullnameLeafCond), SearchCond.getLeaf(groupCond));
 
         assertTrue(subCond.isValid());
 
-        SearchCond cond = SearchCond.getAndCond(subCond, SearchCond.getLeafCond(loginDateCond));
+        SearchCond cond = SearchCond.getAnd(subCond, SearchCond.getLeaf(loginDateCond));
 
         assertTrue(cond.isValid());
 
@@ -104,23 +104,23 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void searchCaseInsensitiveWithLikeCondition() {
-        AttributeCond fullnameLeafCond = new AttributeCond(AttributeCond.Type.ILIKE);
+        AttrCond fullnameLeafCond = new AttrCond(AttrCond.Type.ILIKE);
         fullnameLeafCond.setSchema("fullname");
         fullnameLeafCond.setExpression("%O%");
 
         MembershipCond groupCond = new MembershipCond();
         groupCond.setGroup("root");
 
-        AttributeCond loginDateCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond loginDateCond = new AttrCond(AttrCond.Type.EQ);
         loginDateCond.setSchema("loginDate");
         loginDateCond.setExpression("2009-05-26");
 
-        SearchCond subCond = SearchCond.getAndCond(
-                SearchCond.getLeafCond(fullnameLeafCond), SearchCond.getLeafCond(groupCond));
+        SearchCond subCond = SearchCond.getAnd(
+                SearchCond.getLeaf(fullnameLeafCond), SearchCond.getLeaf(groupCond));
 
         assertTrue(subCond.isValid());
 
-        SearchCond cond = SearchCond.getAndCond(subCond, SearchCond.getLeafCond(loginDateCond));
+        SearchCond cond = SearchCond.getAnd(subCond, SearchCond.getLeaf(loginDateCond));
 
         assertTrue(cond.isValid());
 
@@ -131,11 +131,11 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void searchWithNotCondition() {
-        AttributeCond fullnameLeafCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond fullnameLeafCond = new AttrCond(AttrCond.Type.EQ);
         fullnameLeafCond.setSchema("fullname");
         fullnameLeafCond.setExpression("Giuseppe Verdi");
 
-        SearchCond cond = SearchCond.getNotLeafCond(fullnameLeafCond);
+        SearchCond cond = SearchCond.getNotLeaf(fullnameLeafCond);
         assertTrue(cond.isValid());
 
         List<User> users = searchDAO.search(cond, AnyTypeKind.USER);
@@ -149,11 +149,11 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void searchCaseInsensitiveWithNotCondition() {
-        AttributeCond fullnameLeafCond = new AttributeCond(AttributeCond.Type.IEQ);
+        AttrCond fullnameLeafCond = new AttrCond(AttrCond.Type.IEQ);
         fullnameLeafCond.setSchema("fullname");
         fullnameLeafCond.setExpression("giuseppe verdi");
 
-        SearchCond cond = SearchCond.getNotLeafCond(fullnameLeafCond);
+        SearchCond cond = SearchCond.getNotLeaf(fullnameLeafCond);
         assertTrue(cond.isValid());
 
         List<User> users = searchDAO.search(cond, AnyTypeKind.USER);
@@ -167,11 +167,11 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void searchByBoolean() {
-        AttributeCond coolLeafCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond coolLeafCond = new AttrCond(AttrCond.Type.EQ);
         coolLeafCond.setSchema("cool");
         coolLeafCond.setExpression("true");
 
-        SearchCond cond = SearchCond.getLeafCond(coolLeafCond);
+        SearchCond cond = SearchCond.getLeaf(coolLeafCond);
         assertTrue(cond.isValid());
 
         List<User> users = searchDAO.search(cond, AnyTypeKind.USER);
@@ -183,23 +183,23 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void searchByPageAndSize() {
-        AttributeCond fullnameLeafCond = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond.setSchema("fullname");
         fullnameLeafCond.setExpression("%o%");
 
         MembershipCond groupCond = new MembershipCond();
         groupCond.setGroup("root");
 
-        AttributeCond loginDateCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond loginDateCond = new AttrCond(AttrCond.Type.EQ);
         loginDateCond.setSchema("loginDate");
         loginDateCond.setExpression("2009-05-26");
 
-        SearchCond subCond = SearchCond.getAndCond(
-                SearchCond.getLeafCond(fullnameLeafCond), SearchCond.getLeafCond(groupCond));
+        SearchCond subCond = SearchCond.getAnd(
+                SearchCond.getLeaf(fullnameLeafCond), SearchCond.getLeaf(groupCond));
 
         assertTrue(subCond.isValid());
 
-        SearchCond cond = SearchCond.getAndCond(subCond, SearchCond.getLeafCond(loginDateCond));
+        SearchCond cond = SearchCond.getAnd(subCond, SearchCond.getLeaf(loginDateCond));
 
         assertTrue(cond.isValid());
 
@@ -221,14 +221,14 @@ public class AnySearchTest extends AbstractTest {
         MembershipCond groupCond = new MembershipCond();
         groupCond.setGroup("root");
 
-        List<User> users = searchDAO.search(SearchCond.getLeafCond(groupCond), AnyTypeKind.USER);
+        List<User> users = searchDAO.search(SearchCond.getLeaf(groupCond), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(2, users.size());
 
         groupCond = new MembershipCond();
         groupCond.setGroup("secretary");
 
-        users = searchDAO.search(SearchCond.getNotLeafCond(groupCond), AnyTypeKind.USER);
+        users = searchDAO.search(SearchCond.getNotLeaf(groupCond), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(5, users.size());
     }
@@ -238,7 +238,7 @@ public class AnySearchTest extends AbstractTest {
         RoleCond roleCond = new RoleCond();
         roleCond.setRole("Other");
 
-        List<User> users = searchDAO.search(SearchCond.getLeafCond(roleCond), AnyTypeKind.USER);
+        List<User> users = searchDAO.search(SearchCond.getLeaf(roleCond), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(1, users.size());
     }
@@ -248,24 +248,24 @@ public class AnySearchTest extends AbstractTest {
         PrivilegeCond privilegeCond = new PrivilegeCond();
         privilegeCond.setPrivilege("postMighty");
 
-        List<User> users = searchDAO.search(SearchCond.getLeafCond(privilegeCond), AnyTypeKind.USER);
+        List<User> users = searchDAO.search(SearchCond.getLeaf(privilegeCond), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(1, users.size());
     }
 
     @Test
     public void searchByIsNull() {
-        AttributeCond coolLeafCond = new AttributeCond(AttributeCond.Type.ISNULL);
+        AttrCond coolLeafCond = new AttrCond(AttrCond.Type.ISNULL);
         coolLeafCond.setSchema("cool");
 
-        List<User> users = searchDAO.search(SearchCond.getLeafCond(coolLeafCond), AnyTypeKind.USER);
+        List<User> users = searchDAO.search(SearchCond.getLeaf(coolLeafCond), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(4, users.size());
 
-        coolLeafCond = new AttributeCond(AttributeCond.Type.ISNOTNULL);
+        coolLeafCond = new AttrCond(AttrCond.Type.ISNOTNULL);
         coolLeafCond.setSchema("cool");
 
-        users = searchDAO.search(SearchCond.getLeafCond(coolLeafCond), AnyTypeKind.USER);
+        users = searchDAO.search(SearchCond.getLeaf(coolLeafCond), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(1, users.size());
     }
@@ -278,7 +278,7 @@ public class AnySearchTest extends AbstractTest {
         ResourceCond ws1 = new ResourceCond();
         ws1.setResourceKey("ws-target-resource-list-mappings-2");
 
-        SearchCond searchCondition = SearchCond.getAndCond(SearchCond.getNotLeafCond(ws2), SearchCond.getLeafCond(ws1));
+        SearchCond searchCondition = SearchCond.getAnd(SearchCond.getNotLeaf(ws2), SearchCond.getLeaf(ws1));
         assertTrue(searchCondition.isValid());
 
         List<User> users = searchDAO.search(searchCondition, AnyTypeKind.USER);
@@ -288,11 +288,11 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void searchByBooleanAnyCond() {
-        AttributeCond booleanCond = new AttributeCond(AnyCond.Type.EQ);
+        AttrCond booleanCond = new AttrCond(AnyCond.Type.EQ);
         booleanCond.setSchema("show");
         booleanCond.setExpression("true");
 
-        List<Group> matchingGroups = searchDAO.search(SearchCond.getLeafCond(booleanCond), AnyTypeKind.GROUP);
+        List<Group> matchingGroups = searchDAO.search(SearchCond.getLeaf(booleanCond), AnyTypeKind.GROUP);
         assertNotNull(matchingGroups);
         assertFalse(matchingGroups.isEmpty());
     }
@@ -307,9 +307,9 @@ public class AnySearchTest extends AbstractTest {
         idRightCond.setSchema("key");
         idRightCond.setExpression("2");
 
-        SearchCond searchCondition = SearchCond.getAndCond(
-                SearchCond.getLeafCond(usernameLeafCond),
-                SearchCond.getLeafCond(idRightCond));
+        SearchCond searchCondition = SearchCond.getAnd(
+                SearchCond.getLeaf(usernameLeafCond),
+                SearchCond.getLeaf(idRightCond));
 
         List<User> matching = searchDAO.search(searchCondition, AnyTypeKind.USER);
         assertNotNull(matching);
@@ -328,9 +328,9 @@ public class AnySearchTest extends AbstractTest {
         idRightCond.setSchema("key");
         idRightCond.setExpression("37d15e4c-cdc1-460b-a591-8505c8133806");
 
-        SearchCond searchCondition = SearchCond.getAndCond(
-                SearchCond.getLeafCond(groupNameLeafCond),
-                SearchCond.getLeafCond(idRightCond));
+        SearchCond searchCondition = SearchCond.getAnd(
+                SearchCond.getLeaf(groupNameLeafCond),
+                SearchCond.getLeaf(idRightCond));
 
         assertTrue(searchCondition.isValid());
 
@@ -347,13 +347,13 @@ public class AnySearchTest extends AbstractTest {
         usernameLeafCond.setSchema("username");
         usernameLeafCond.setExpression("rossini");
 
-        AttributeCond idRightCond = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond idRightCond = new AttrCond(AttrCond.Type.LIKE);
         idRightCond.setSchema("fullname");
         idRightCond.setExpression("Giuseppe V%");
 
-        SearchCond searchCondition = SearchCond.getOrCond(
-                SearchCond.getLeafCond(usernameLeafCond),
-                SearchCond.getLeafCond(idRightCond));
+        SearchCond searchCondition = SearchCond.getOr(
+                SearchCond.getLeaf(usernameLeafCond),
+                SearchCond.getLeaf(idRightCond));
 
         List<User> matchingUsers = searchDAO.search(
                 searchCondition, AnyTypeKind.USER);
@@ -367,13 +367,13 @@ public class AnySearchTest extends AbstractTest {
         usernameLeafCond.setSchema("username");
         usernameLeafCond.setExpression("RoSsini");
 
-        AttributeCond idRightCond = new AttributeCond(AttributeCond.Type.ILIKE);
+        AttrCond idRightCond = new AttrCond(AttrCond.Type.ILIKE);
         idRightCond.setSchema("fullname");
         idRightCond.setExpression("gIuseppe v%");
 
-        SearchCond searchCondition = SearchCond.getOrCond(
-                SearchCond.getLeafCond(usernameLeafCond),
-                SearchCond.getLeafCond(idRightCond));
+        SearchCond searchCondition = SearchCond.getOr(
+                SearchCond.getLeaf(usernameLeafCond),
+                SearchCond.getLeaf(idRightCond));
 
         List<User> matchingUsers = searchDAO.search(
                 searchCondition, AnyTypeKind.USER);
@@ -387,7 +387,7 @@ public class AnySearchTest extends AbstractTest {
         idLeafCond.setSchema("key");
         idLeafCond.setExpression("74cd8ece-715a-44a4-a736-e17b46c4e7e6");
 
-        SearchCond searchCondition = SearchCond.getLeafCond(idLeafCond);
+        SearchCond searchCondition = SearchCond.getLeaf(idLeafCond);
         assertTrue(searchCondition.isValid());
 
         List<User> users = searchDAO.search(searchCondition, AnyTypeKind.USER);
@@ -401,7 +401,7 @@ public class AnySearchTest extends AbstractTest {
         AnyTypeCond tcond = new AnyTypeCond();
         tcond.setAnyTypeKey("PRINTER");
 
-        SearchCond searchCondition = SearchCond.getLeafCond(tcond);
+        SearchCond searchCondition = SearchCond.getLeaf(tcond);
         assertTrue(searchCondition.isValid());
 
         List<AnyObject> printers = searchDAO.search(searchCondition, AnyTypeKind.ANY_OBJECT);
@@ -423,8 +423,8 @@ public class AnySearchTest extends AbstractTest {
         AnyTypeCond tcond = new AnyTypeCond();
         tcond.setAnyTypeKey("PRINTER");
 
-        SearchCond searchCondition = SearchCond.getAndCond(
-                SearchCond.getLeafCond(relationshipTypeCond), SearchCond.getLeafCond(tcond));
+        SearchCond searchCondition = SearchCond.getAnd(
+                SearchCond.getLeaf(relationshipTypeCond), SearchCond.getLeaf(tcond));
         assertTrue(searchCondition.isValid());
 
         List<AnyObject> anyObjects = searchDAO.search(searchCondition, AnyTypeKind.ANY_OBJECT);
@@ -434,7 +434,7 @@ public class AnySearchTest extends AbstractTest {
         assertTrue(anyObjects.stream().anyMatch(any -> "8559d14d-58c2-46eb-a2d4-a7d35161e8f8".equals(any.getKey())));
 
         // 2. search for users involved in "neighborhood" relationship
-        searchCondition = SearchCond.getLeafCond(relationshipTypeCond);
+        searchCondition = SearchCond.getLeaf(relationshipTypeCond);
         List<User> users = searchDAO.search(searchCondition, AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(1, users.size());
@@ -446,11 +446,11 @@ public class AnySearchTest extends AbstractTest {
         AnyCond usernameLeafCond = new AnyCond(AnyCond.Type.EQ);
         usernameLeafCond.setSchema("username");
         usernameLeafCond.setExpression("rossini");
-        AttributeCond idRightCond = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond idRightCond = new AttrCond(AttrCond.Type.LIKE);
         idRightCond.setSchema("fullname");
         idRightCond.setExpression("Giuseppe V%");
-        SearchCond searchCondition = SearchCond.getOrCond(
-                SearchCond.getLeafCond(usernameLeafCond), SearchCond.getLeafCond(idRightCond));
+        SearchCond searchCondition = SearchCond.getOr(
+                SearchCond.getLeaf(usernameLeafCond), SearchCond.getLeaf(idRightCond));
 
         List<OrderByClause> orderByClauses = new ArrayList<>();
         OrderByClause orderByClause = new OrderByClause();
@@ -473,7 +473,7 @@ public class AnySearchTest extends AbstractTest {
         AnyCond idLeafCond = new AnyCond(AnyCond.Type.LIKE);
         idLeafCond.setSchema("name");
         idLeafCond.setExpression("%r");
-        SearchCond searchCondition = SearchCond.getLeafCond(idLeafCond);
+        SearchCond searchCondition = SearchCond.getLeaf(idLeafCond);
         assertTrue(searchCondition.isValid());
 
         OrderByClause orderByClause = new OrderByClause();
@@ -490,7 +490,7 @@ public class AnySearchTest extends AbstractTest {
     public void assignable() {
         AssignableCond assignableCond = new AssignableCond();
         assignableCond.setRealmFullPath("/even/two");
-        SearchCond searchCondition = SearchCond.getLeafCond(assignableCond);
+        SearchCond searchCondition = SearchCond.getLeaf(assignableCond);
         assertTrue(searchCondition.isValid());
 
         List<Group> groups = searchDAO.search(searchCondition, AnyTypeKind.GROUP);
@@ -499,7 +499,7 @@ public class AnySearchTest extends AbstractTest {
 
         assignableCond = new AssignableCond();
         assignableCond.setRealmFullPath("/odd");
-        searchCondition = SearchCond.getLeafCond(assignableCond);
+        searchCondition = SearchCond.getLeaf(assignableCond);
         assertTrue(searchCondition.isValid());
 
         List<AnyObject> anyObjects = searchDAO.search(searchCondition, AnyTypeKind.ANY_OBJECT);
@@ -511,7 +511,7 @@ public class AnySearchTest extends AbstractTest {
     public void member() {
         MemberCond memberCond = new MemberCond();
         memberCond.setMember("1417acbe-cbf6-4277-9372-e75e04f97000");
-        SearchCond searchCondition = SearchCond.getLeafCond(memberCond);
+        SearchCond searchCondition = SearchCond.getLeaf(memberCond);
         assertTrue(searchCondition.isValid());
 
         List<Group> groups = searchDAO.search(searchCondition, AnyTypeKind.GROUP);
@@ -529,7 +529,7 @@ public class AnySearchTest extends AbstractTest {
         ws1.setResourceKey("ws-target-resource-list-mappings-1");
 
         SearchCond searchCondition =
-                SearchCond.getAndCond(SearchCond.getNotLeafCond(ws2), SearchCond.getNotLeafCond(ws1));
+                SearchCond.getAnd(SearchCond.getNotLeaf(ws2), SearchCond.getNotLeaf(ws1));
         assertTrue(searchCondition.isValid());
 
         List<User> users = searchDAO.search(searchCondition, AnyTypeKind.USER);
@@ -540,11 +540,11 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void issue242() {
-        AnyCond cond = new AnyCond(AttributeCond.Type.LIKE);
+        AnyCond cond = new AnyCond(AttrCond.Type.LIKE);
         cond.setSchema("key");
         cond.setExpression("test%");
 
-        SearchCond searchCondition = SearchCond.getLeafCond(cond);
+        SearchCond searchCondition = SearchCond.getLeaf(cond);
         assertTrue(searchCondition.isValid());
 
         List<User> users = searchDAO.search(searchCondition, AnyTypeKind.USER);
@@ -554,11 +554,11 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void issueSYNCOPE46() {
-        AnyCond cond = new AnyCond(AttributeCond.Type.LIKE);
+        AnyCond cond = new AnyCond(AttrCond.Type.LIKE);
         cond.setSchema("username");
         cond.setExpression("%ossin%");
 
-        SearchCond searchCondition = SearchCond.getLeafCond(cond);
+        SearchCond searchCondition = SearchCond.getLeaf(cond);
         assertTrue(searchCondition.isValid());
 
         List<User> users = searchDAO.search(searchCondition, AnyTypeKind.USER);
@@ -568,15 +568,15 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void issueSYNCOPE433() {
-        AttributeCond isNullCond = new AttributeCond(AttributeCond.Type.ISNULL);
+        AttrCond isNullCond = new AttrCond(AttrCond.Type.ISNULL);
         isNullCond.setSchema("loginDate");
 
-        AnyCond likeCond = new AnyCond(AttributeCond.Type.LIKE);
+        AnyCond likeCond = new AnyCond(AttrCond.Type.LIKE);
         likeCond.setSchema("username");
         likeCond.setExpression("%ossin%");
 
-        SearchCond searchCond = SearchCond.getOrCond(
-                SearchCond.getLeafCond(isNullCond), SearchCond.getLeafCond(likeCond));
+        SearchCond searchCond = SearchCond.getOr(
+                SearchCond.getLeaf(isNullCond), SearchCond.getLeaf(likeCond));
 
         Integer count = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, searchCond, AnyTypeKind.USER);
         assertNotNull(count);
@@ -585,24 +585,24 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void issueSYNCOPE929() {
-        AttributeCond rossiniCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond rossiniCond = new AttrCond(AttrCond.Type.EQ);
         rossiniCond.setSchema("surname");
         rossiniCond.setExpression("Rossini");
 
-        AttributeCond genderCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond genderCond = new AttrCond(AttrCond.Type.EQ);
         genderCond.setSchema("gender");
         genderCond.setExpression("M");
 
         SearchCond orCond =
-                SearchCond.getOrCond(SearchCond.getLeafCond(rossiniCond),
-                        SearchCond.getLeafCond(genderCond));
+                SearchCond.getOr(SearchCond.getLeaf(rossiniCond),
+                        SearchCond.getLeaf(genderCond));
 
-        AttributeCond belliniCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond belliniCond = new AttrCond(AttrCond.Type.EQ);
         belliniCond.setSchema("surname");
         belliniCond.setExpression("Bellini");
 
         SearchCond searchCond =
-                SearchCond.getAndCond(orCond, SearchCond.getLeafCond(belliniCond));
+                SearchCond.getAnd(orCond, SearchCond.getLeaf(belliniCond));
 
         List<User> users = searchDAO.search(searchCond, AnyTypeKind.USER);
         assertNotNull(users);
@@ -643,7 +643,7 @@ public class AnySearchTest extends AbstractTest {
         MembershipCond groupCond = new MembershipCond();
         groupCond.setGroup("citizen");
 
-        SearchCond searchCondition = SearchCond.getLeafCond(groupCond);
+        SearchCond searchCondition = SearchCond.getLeaf(groupCond);
 
         List<AnyObject> matching = searchDAO.search(searchCondition, AnyTypeKind.ANY_OBJECT);
         assertEquals(2, matching.size());
@@ -651,8 +651,8 @@ public class AnySearchTest extends AbstractTest {
         AnyTypeCond anyTypeCond = new AnyTypeCond();
         anyTypeCond.setAnyTypeKey(service.getKey());
 
-        searchCondition = SearchCond.getAndCond(
-                SearchCond.getLeafCond(groupCond), SearchCond.getLeafCond(anyTypeCond));
+        searchCondition = SearchCond.getAnd(
+                SearchCond.getLeaf(groupCond), SearchCond.getLeaf(anyTypeCond));
 
         matching = searchDAO.search(searchCondition, AnyTypeKind.ANY_OBJECT);
         assertEquals(1, matching.size());
@@ -660,7 +660,7 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void issueSYNCOPE983() {
-        AttributeCond fullnameLeafCond = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond fullnameLeafCond = new AttrCond(AttrCond.Type.LIKE);
         fullnameLeafCond.setSchema("surname");
         fullnameLeafCond.setExpression("%o%");
 
@@ -676,7 +676,7 @@ public class AnySearchTest extends AbstractTest {
 
         List<User> users = searchDAO.search(
                 SyncopeConstants.FULL_ADMIN_REALMS,
-                SearchCond.getLeafCond(fullnameLeafCond),
+                SearchCond.getLeaf(fullnameLeafCond),
                 -1,
                 -1,
                 orderByClauses,
@@ -686,14 +686,14 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void issueSYNCOPE1416() {
-        AttributeCond idLeftCond = new AttributeCond(AttributeCond.Type.ISNOTNULL);
+        AttrCond idLeftCond = new AttrCond(AttrCond.Type.ISNOTNULL);
         idLeftCond.setSchema("surname");
 
-        AttributeCond idRightCond = new AttributeCond(AttributeCond.Type.ISNOTNULL);
+        AttrCond idRightCond = new AttrCond(AttrCond.Type.ISNOTNULL);
         idRightCond.setSchema("firstname");
 
-        SearchCond searchCondition = SearchCond.getAndCond(
-                SearchCond.getLeafCond(idLeftCond), SearchCond.getLeafCond(idRightCond));
+        SearchCond searchCondition = SearchCond.getAnd(
+                SearchCond.getLeaf(idLeftCond), SearchCond.getLeaf(idRightCond));
 
         List<OrderByClause> orderByClauses = new ArrayList<>();
         OrderByClause orderByClause = new OrderByClause();
@@ -707,19 +707,19 @@ public class AnySearchTest extends AbstractTest {
                 users.size());
 
         // search by attribute with unique constraint
-        AttributeCond fullnameCond = new AttributeCond(AttributeCond.Type.ISNOTNULL);
+        AttrCond fullnameCond = new AttrCond(AttrCond.Type.ISNOTNULL);
         fullnameCond.setSchema("fullname");
 
-        SearchCond cond = SearchCond.getLeafCond(fullnameCond);
+        SearchCond cond = SearchCond.getLeaf(fullnameCond);
         assertTrue(cond.isValid());
 
         users = searchDAO.search(cond, AnyTypeKind.USER);
         assertEquals(5, users.size());
 
-        fullnameCond = new AttributeCond(AttributeCond.Type.ISNULL);
+        fullnameCond = new AttrCond(AttrCond.Type.ISNULL);
         fullnameCond.setSchema("fullname");
 
-        cond = SearchCond.getLeafCond(fullnameCond);
+        cond = SearchCond.getLeaf(fullnameCond);
         assertTrue(cond.isValid());
 
         users = searchDAO.search(cond, AnyTypeKind.USER);
@@ -728,11 +728,11 @@ public class AnySearchTest extends AbstractTest {
 
     @Test
     public void issueSYNCOPE1419() {
-        AttributeCond loginDateCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond loginDateCond = new AttrCond(AttrCond.Type.EQ);
         loginDateCond.setSchema("loginDate");
         loginDateCond.setExpression("2009-05-26");
 
-        SearchCond cond = SearchCond.getNotLeafCond(loginDateCond);
+        SearchCond cond = SearchCond.getNotLeaf(loginDateCond);
         assertTrue(cond.isValid());
 
         List<User> users = searchDAO.search(cond, AnyTypeKind.USER);
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 a659697..a1d4f4c 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
@@ -35,7 +35,7 @@ import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 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.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 import org.apache.syncope.core.persistence.api.dao.search.RoleCond;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
@@ -92,7 +92,7 @@ public class AnySearchTest extends AbstractTest {
         RoleCond roleCond = new RoleCond();
         roleCond.setRole(role.getKey());
 
-        List<User> users = searchDAO.search(SearchCond.getLeafCond(roleCond), AnyTypeKind.USER);
+        List<User> users = searchDAO.search(SearchCond.getLeaf(roleCond), AnyTypeKind.USER);
         assertNotNull(users);
         assertEquals(1, users.size());
         assertEquals("c9b2dec2-00a7-4855-97c0-d854842b4b24", users.get(0).getKey());
@@ -103,11 +103,11 @@ public class AnySearchTest extends AbstractTest {
         groupDAO.findAll(1, 100).forEach(group -> groupDAO.delete(group.getKey()));
         entityManager().flush();
 
-        AttributeCond coolLeafCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond coolLeafCond = new AttrCond(AttrCond.Type.EQ);
         coolLeafCond.setSchema("cool");
         coolLeafCond.setExpression("true");
 
-        SearchCond cond = SearchCond.getLeafCond(coolLeafCond);
+        SearchCond cond = SearchCond.getLeaf(coolLeafCond);
         assertTrue(cond.isValid());
 
         List<User> users = searchDAO.search(cond, AnyTypeKind.USER);
@@ -122,11 +122,11 @@ public class AnySearchTest extends AbstractTest {
         AnyCond usernameLeafCond = new AnyCond(AnyCond.Type.EQ);
         usernameLeafCond.setSchema("username");
         usernameLeafCond.setExpression("rossini");
-        AttributeCond idRightCond = new AttributeCond(AttributeCond.Type.LIKE);
+        AttrCond idRightCond = new AttrCond(AttrCond.Type.LIKE);
         idRightCond.setSchema("fullname");
         idRightCond.setExpression("Giuseppe V%");
-        SearchCond searchCondition = SearchCond.getOrCond(
-                SearchCond.getLeafCond(usernameLeafCond), SearchCond.getLeafCond(idRightCond));
+        SearchCond searchCondition = SearchCond.getOr(
+                SearchCond.getLeaf(usernameLeafCond), SearchCond.getLeaf(idRightCond));
 
         List<OrderByClause> orderByClauses = new ArrayList<>();
         OrderByClause orderByClause = new OrderByClause();
@@ -169,19 +169,19 @@ public class AnySearchTest extends AbstractTest {
 
         entityManager().flush();
 
-        AttributeCond titleCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond titleCond = new AttrCond(AttrCond.Type.EQ);
         titleCond.setSchema("title");
         titleCond.setExpression("syncope's group");
 
-        List<Group> matching = searchDAO.search(SearchCond.getLeafCond(titleCond), AnyTypeKind.GROUP);
+        List<Group> matching = searchDAO.search(SearchCond.getLeaf(titleCond), AnyTypeKind.GROUP);
         assertEquals(1, matching.size());
         assertEquals(group.getKey(), matching.get(0).getKey());
 
-        AttributeCond originalNameCond = new AttributeCond(AttributeCond.Type.EQ);
+        AttrCond originalNameCond = new AttrCond(AttrCond.Type.EQ);
         originalNameCond.setSchema("originalName");
         originalNameCond.setExpression("syncope's group");
 
-        matching = searchDAO.search(SearchCond.getLeafCond(originalNameCond), AnyTypeKind.GROUP);
+        matching = searchDAO.search(SearchCond.getLeaf(originalNameCond), AnyTypeKind.GROUP);
         assertEquals(1, matching.size());
         assertEquals(group.getKey(), matching.get(0).getKey());
     }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/DynRealmTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/DynRealmTest.java
index 69f36e7..912aea2 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/DynRealmTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/DynRealmTest.java
@@ -87,12 +87,12 @@ public class DynRealmTest extends AbstractTest {
 
         DynRealmCond dynRealmCond = new DynRealmCond();
         dynRealmCond.setDynRealm(actual.getKey());
-        List<User> matching = searchDAO.search(SearchCond.getLeafCond(dynRealmCond), AnyTypeKind.USER);
+        List<User> matching = searchDAO.search(SearchCond.getLeaf(dynRealmCond), AnyTypeKind.USER);
         assertNotNull(matching);
         assertFalse(matching.isEmpty());
 
         User user = matching.get(0);
-        assertTrue(anyMatcher.matches(user, SearchCond.getLeafCond(dynRealmCond)));
+        assertTrue(anyMatcher.matches(user, SearchCond.getLeaf(dynRealmCond)));
 
         assertTrue(userDAO.findDynRealms(user.getKey()).contains(actual.getKey()));
     }
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 8c7fa09..3e8e6c8 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -2426,62 +2426,27 @@ $$ }&#10;
   
   <SecurityQuestion id="887028ea-66fc-41e7-b397-620d7ea6dfbb" content="What's your mother's maiden name?"/>
 
-  <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
+  <GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"                predicates="[{&quot;cond&quot;:null,&quot;factory&quot;:&quot;METHOD&quot;,&quot;args&quot;:&quot;GET&quot;}]"/>
 
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logName="syncope.audit.[LOGIC]:[SyncopeLogic]:[]:[isSelfRegAllowed]:[SUCCESS]" logLevel="DEBUG" logType="AUDIT"/>
 
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[confirmPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[mustChangePassword]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[requestPasswordReset]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfCreate]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfDelete]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfRead]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfStatus]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[selfUpdate]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[status]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
-  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[FAILURE]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
 
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
@@ -2496,6 +2461,13 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
 
-  <GatewayRoute id="ec7bada2-3dd6-460c-8441-65521d005ffa" name="basic1" target="http://httpbin.org:80" status="PUBLISHED"
-                predicates="[{&quot;cond&quot;:null,&quot;factory&quot;:&quot;METHOD&quot;,&quot;args&quot;:&quot;GET&quot;}]"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[delete]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[link]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[provision]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
+  <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
 </dataset>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/DynRealmDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/DynRealmDataBinderImpl.java
index 0f00911..4a5c4c9 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/DynRealmDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/DynRealmDataBinderImpl.java
@@ -30,6 +30,7 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.DynRealm;
 import org.apache.syncope.core.persistence.api.entity.DynRealmMembership;
 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.DynRealmDataBinder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,8 +51,11 @@ public class DynRealmDataBinderImpl implements DynRealmDataBinder {
     @Autowired
     private EntityFactory entityFactory;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     private void setDynMembership(final DynRealm dynRealm, final AnyType anyType, final String dynMembershipFIQL) {
-        SearchCond dynMembershipCond = SearchCondConverter.convert(dynMembershipFIQL);
+        SearchCond dynMembershipCond = SearchCondConverter.convert(searchCondVisitor, dynMembershipFIQL);
         if (!dynMembershipCond.isValid()) {
             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression);
             sce.getElements().add(dynMembershipFIQL);
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 1473247..7368bef 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
@@ -53,6 +53,7 @@ import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
 import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
@@ -64,8 +65,11 @@ public class GroupDataBinderImpl extends AbstractAnyDataBinder implements GroupD
     @Autowired
     private AnyTypeDAO anyTypeDAO;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     private void setDynMembership(final Group group, final AnyType anyType, final String dynMembershipFIQL) {
-        SearchCond dynMembershipCond = SearchCondConverter.convert(dynMembershipFIQL);
+        SearchCond dynMembershipCond = SearchCondConverter.convert(searchCondVisitor, dynMembershipFIQL);
         if (!dynMembershipCond.isValid()) {
             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression);
             sce.getElements().add(dynMembershipFIQL);
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 85595f6..bfe3059 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
@@ -35,6 +35,7 @@ import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.Role;
 import org.apache.syncope.core.persistence.api.entity.user.DynRoleMembership;
+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;
@@ -61,8 +62,11 @@ public class RoleDataBinderImpl implements RoleDataBinder {
     @Autowired
     private EntityFactory entityFactory;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     private void setDynMembership(final Role role, final String dynMembershipFIQL) {
-        SearchCond dynMembershipCond = SearchCondConverter.convert(dynMembershipFIQL);
+        SearchCond dynMembershipCond = SearchCondConverter.convert(searchCondVisitor, dynMembershipFIQL);
         if (!dynMembershipCond.isValid()) {
             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidSearchExpression);
             sce.getElements().add(dynMembershipFIQL);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
index 7b1dc3e..6bce3fc 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/GroupMemberProvisionTaskJobDelegate.java
@@ -86,7 +86,7 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
 
         MembershipCond membershipCond = new MembershipCond();
         membershipCond.setGroup(groupKey);
-        List<User> users = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.USER);
+        List<User> users = searchDAO.search(SearchCond.getLeaf(membershipCond), AnyTypeKind.USER);
         Collection<String> groupResourceKeys = groupDAO.findAllResourceKeys(groupKey);
         status.set("About to "
                 + (action == ProvisionAction.DEPROVISION ? "de" : "") + "provision "
@@ -115,7 +115,7 @@ public class GroupMemberProvisionTaskJobDelegate extends AbstractSchedTaskJobDel
 
         membershipCond = new MembershipCond();
         membershipCond.setGroup(groupKey);
-        List<AnyObject> anyObjects = searchDAO.search(SearchCond.getLeafCond(membershipCond), AnyTypeKind.ANY_OBJECT);
+        List<AnyObject> anyObjects = searchDAO.search(SearchCond.getLeaf(membershipCond), AnyTypeKind.ANY_OBJECT);
         status.set("About to "
                 + (action == ProvisionAction.DEPROVISION ? "de" : "") + "provision "
                 + anyObjects.size() + " any objects from " + groupResourceKeys);
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
index adc25ca..55db107 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/GroupReportlet.java
@@ -39,6 +39,7 @@ import org.apache.syncope.core.persistence.api.search.SearchCondConverter;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.xml.sax.ContentHandler;
@@ -57,6 +58,9 @@ public class GroupReportlet extends AbstractReportlet {
     @Autowired
     private GroupDataBinder groupDataBinder;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     private GroupReportletConf conf;
 
     private static void doExtractResources(final ContentHandler handler, final AnyTO anyTO)
@@ -284,7 +288,7 @@ public class GroupReportlet extends AbstractReportlet {
         return StringUtils.isBlank(conf.getMatchingCond())
                 ? groupDAO.count()
                 : searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS,
-                        SearchCondConverter.convert(conf.getMatchingCond()), AnyTypeKind.GROUP);
+                        SearchCondConverter.convert(searchCondVisitor, conf.getMatchingCond()), AnyTypeKind.GROUP);
     }
 
     @Override
@@ -316,7 +320,7 @@ public class GroupReportlet extends AbstractReportlet {
             } else {
                 groups = searchDAO.search(
                         SyncopeConstants.FULL_ADMIN_REALMS,
-                        SearchCondConverter.convert(this.conf.getMatchingCond()),
+                        SearchCondConverter.convert(searchCondVisitor, this.conf.getMatchingCond()),
                         page,
                         AnyDAO.DEFAULT_PAGE_SIZE,
                         List.of(),
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
index 70077db..de53da9 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/ReconciliationReportlet.java
@@ -52,6 +52,7 @@ import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.ConnectorFactory;
 import org.apache.syncope.core.provisioning.api.MappingManager;
@@ -96,6 +97,9 @@ public class ReconciliationReportlet extends AbstractReportlet {
     @Autowired
     private AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     private ReconciliationReportletConf conf;
 
     private static String getAnyElementName(final AnyTypeKind anyTypeKind) {
@@ -382,7 +386,7 @@ public class ReconciliationReportlet extends AbstractReportlet {
                 doExtract(handler, userDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
             }
         } else {
-            SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
+            SearchCond cond = SearchCondConverter.convert(searchCondVisitor, this.conf.getUserMatchingCond());
 
             int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.USER);
             int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
@@ -422,7 +426,7 @@ public class ReconciliationReportlet extends AbstractReportlet {
                 doExtract(handler, groupDAO.findAll(page, AnyDAO.DEFAULT_PAGE_SIZE));
             }
         } else {
-            SearchCond cond = SearchCondConverter.convert(this.conf.getUserMatchingCond());
+            SearchCond cond = SearchCondConverter.convert(searchCondVisitor, this.conf.getUserMatchingCond());
 
             int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.GROUP);
             int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
@@ -451,10 +455,10 @@ public class ReconciliationReportlet extends AbstractReportlet {
                 AnyTypeCond anyTypeCond = new AnyTypeCond();
                 anyTypeCond.setAnyTypeKey(anyType.getKey());
                 SearchCond cond = StringUtils.isBlank(this.conf.getAnyObjectMatchingCond())
-                        ? SearchCond.getLeafCond(anyTypeCond)
-                        : SearchCond.getAndCond(
-                                SearchCond.getLeafCond(anyTypeCond),
-                                SearchCondConverter.convert(this.conf.getAnyObjectMatchingCond()));
+                        ? SearchCond.getLeaf(anyTypeCond)
+                        : SearchCond.getAnd(
+                                SearchCond.getLeaf(anyTypeCond),
+                                SearchCondConverter.convert(searchCondVisitor, this.conf.getAnyObjectMatchingCond()));
 
                 int total = searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS, cond, AnyTypeKind.ANY_OBJECT);
                 int pages = (total / AnyDAO.DEFAULT_PAGE_SIZE) + 1;
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
index d826799..7b5c1cd 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/report/UserReportlet.java
@@ -43,6 +43,7 @@ import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.ReportletConfClass;
 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.search.SearchCondVisitor;
 import org.apache.syncope.core.provisioning.api.data.AnyObjectDataBinder;
 import org.apache.syncope.core.provisioning.api.data.GroupDataBinder;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
@@ -69,6 +70,9 @@ public class UserReportlet extends AbstractReportlet {
     @Autowired
     private AnyObjectDataBinder anyObjectDataBinder;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     private UserReportletConf conf;
 
     private static void doExtractResources(final ContentHandler handler, final AnyTO anyTO)
@@ -345,7 +349,7 @@ public class UserReportlet extends AbstractReportlet {
         return StringUtils.isBlank(conf.getMatchingCond())
                 ? userDAO.count()
                 : searchDAO.count(SyncopeConstants.FULL_ADMIN_REALMS,
-                        SearchCondConverter.convert(conf.getMatchingCond()), AnyTypeKind.USER);
+                        SearchCondConverter.convert(searchCondVisitor, this.conf.getMatchingCond()), AnyTypeKind.USER);
     }
 
     @Override
@@ -377,7 +381,7 @@ public class UserReportlet extends AbstractReportlet {
             } else {
                 users = searchDAO.search(
                         SyncopeConstants.FULL_ADMIN_REALMS,
-                        SearchCondConverter.convert(this.conf.getMatchingCond()),
+                        SearchCondConverter.convert(searchCondVisitor, this.conf.getMatchingCond()),
                         page,
                         AnyDAO.DEFAULT_PAGE_SIZE,
                         List.of(),
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 7a19fb0..37a13e7 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
@@ -63,6 +63,7 @@ import org.apache.syncope.core.persistence.api.entity.user.UMembership;
 import org.apache.syncope.core.persistence.api.entity.user.UPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.user.User;
 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.VirAttrHandler;
@@ -154,6 +155,9 @@ public class DefaultNotificationManager implements NotificationManager {
     @Autowired
     private IntAttrNameParser intAttrNameParser;
 
+    @Autowired
+    private SearchCondVisitor searchCondVisitor;
+
     @Transactional(readOnly = true)
     @Override
     public long getMaxRetries() {
@@ -181,7 +185,7 @@ public class DefaultNotificationManager implements NotificationManager {
 
         if (notification.getRecipientsFIQL() != null) {
             recipients.addAll(searchDAO.<User>search(
-                    SearchCondConverter.convert(notification.getRecipientsFIQL()),
+                    SearchCondConverter.convert(searchCondVisitor, notification.getRecipientsFIQL()),
                     List.of(), AnyTypeKind.USER));
         }
 
@@ -338,9 +342,9 @@ public class DefaultNotificationManager implements NotificationManager {
                 if (!notification.getEvents().contains(currentEvent)) {
                     LOG.debug("No events found about {}", any);
                 } else if (anyType == null || any == null
-                        || notification.getAbout(anyType).isEmpty()
-                        || anyMatchDAO.matches(
-                                any, SearchCondConverter.convert(notification.getAbout(anyType).get().get()))) {
+                        || !notification.getAbout(anyType).isPresent()
+                        || anyMatchDAO.matches(any, SearchCondConverter.convert(
+                                searchCondVisitor, notification.getAbout(anyType).get().get()))) {
 
                     LOG.debug("Creating notification task for event {} about {}", currentEvent, any);
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
index 1c5250d..f94a7d8 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/DefaultRealmPullResultHandler.java
@@ -35,7 +35,7 @@ import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+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.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
@@ -553,9 +553,9 @@ public class DefaultRealmPullResultHandler
                         }
 
                         Set<String> adminRealms = Set.of(realm.getFullPath());
-                        AnyCond keyCond = new AnyCond(AttributeCond.Type.ISNOTNULL);
+                        AnyCond keyCond = new AnyCond(AttrCond.Type.ISNOTNULL);
                         keyCond.setSchema("key");
-                        SearchCond allMatchingCond = SearchCond.getLeafCond(keyCond);
+                        SearchCond allMatchingCond = SearchCond.getLeaf(keyCond);
                         int users = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.USER);
                         int groups = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.GROUP);
                         int anyObjects = searchDAO.count(adminRealms, allMatchingCond, AnyTypeKind.ANY_OBJECT);
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 bb7c880..a7ec5f3 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
@@ -46,7 +46,7 @@ import org.apache.syncope.core.persistence.api.dao.PullMatch;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
 import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+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.AnyUtils;
@@ -240,10 +240,10 @@ public class InboundMatcher {
 
                 case "username":
                     if (anyTypeKind == AnyTypeKind.USER && ignoreCaseMatch) {
-                        AnyCond cond = new AnyCond(AttributeCond.Type.IEQ);
+                        AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
                         cond.setSchema("username");
                         cond.setExpression(finalConnObjectKeyValue);
-                        anys.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.USER));
+                        anys.addAll(searchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.USER));
                     } else {
                         Optional.ofNullable(userDAO.findByUsername(finalConnObjectKeyValue)).ifPresent(anys::add);
                     }
@@ -251,19 +251,19 @@ public class InboundMatcher {
 
                 case "name":
                     if (anyTypeKind == AnyTypeKind.GROUP && ignoreCaseMatch) {
-                        AnyCond cond = new AnyCond(AttributeCond.Type.IEQ);
+                        AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
                         cond.setSchema("name");
                         cond.setExpression(finalConnObjectKeyValue);
-                        anys.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.GROUP));
+                        anys.addAll(searchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.GROUP));
                     } else {
                         Optional.ofNullable(groupDAO.findByName(finalConnObjectKeyValue)).ifPresent(anys::add);
                     }
 
                     if (anyTypeKind == AnyTypeKind.ANY_OBJECT && ignoreCaseMatch) {
-                        AnyCond cond = new AnyCond(AttributeCond.Type.IEQ);
+                        AnyCond cond = new AnyCond(AttrCond.Type.IEQ);
                         cond.setSchema("name");
                         cond.setExpression(finalConnObjectKeyValue);
-                        anys.addAll(searchDAO.search(SearchCond.getLeafCond(cond), AnyTypeKind.ANY_OBJECT));
+                        anys.addAll(searchDAO.search(SearchCond.getLeaf(cond), AnyTypeKind.ANY_OBJECT));
                     } else {
                         Optional.ofNullable(anyObjectDAO.findByName(finalConnObjectKeyValue)).ifPresent(anys::add);
                     }
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 0e47f3a..2638b96 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
@@ -45,6 +45,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.task.PushTaskAnyFilter;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.api.search.SearchCondVisitor;
 import org.apache.syncope.core.provisioning.api.Connector;
 import org.apache.syncope.core.provisioning.api.pushpull.AnyObjectPushResultHandler;
 import org.apache.syncope.core.provisioning.api.pushpull.GroupPushResultHandler;
@@ -72,6 +73,9 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
     @Autowired
     protected AnyUtilsFactory anyUtilsFactory;
 
+    @Autowired
+    protected SearchCondVisitor searchCondVisitor;
+
     protected ProvisioningProfile<PushTask, PushActions> profile;
 
     protected final Map<String, MutablePair<Integer, String>> handled = new HashMap<>();
@@ -224,7 +228,7 @@ public class PushJobDelegate extends AbstractProvisioningJobDelegate<PushTask> {
                 String filter = anyFilter.map(PushTaskAnyFilter::getFIQLCond).orElse(null);
                 SearchCond cond = StringUtils.isBlank(filter)
                         ? anyDAO.getAllMatchingCond()
-                        : SearchCondConverter.convert(filter);
+                        : SearchCondConverter.convert(searchCondVisitor, filter);
                 int count = searchDAO.count(
                         Set.of(profile.getTask().getSourceRealm().getFullPath()),
                         cond,
diff --git a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
index 4deb963..38d5e0b 100644
--- a/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
+++ b/core/spring/src/main/java/org/apache/syncope/core/spring/security/AuthDataAccessor.java
@@ -43,7 +43,7 @@ import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 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.AttributeCond;
+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.AccessToken;
 import org.apache.syncope.core.persistence.api.entity.Entity;
@@ -168,10 +168,10 @@ public class AuthDataAccessor {
             if ("username".equals(authAttrValues.get(i))) {
                 user = userDAO.findByUsername(authentication.getName());
             } else {
-                AttributeCond attrCond = new AttributeCond(AttributeCond.Type.EQ);
+                AttrCond attrCond = new AttrCond(AttrCond.Type.EQ);
                 attrCond.setSchema(authAttrValues.get(i));
                 attrCond.setExpression(authentication.getName());
-                List<User> users = searchDAO.search(SearchCond.getLeafCond(attrCond), AnyTypeKind.USER);
+                List<User> users = searchDAO.search(SearchCond.getLeaf(attrCond), AnyTypeKind.USER);
                 if (users.size() == 1) {
                     user = users.get(0);
                 } else {
diff --git a/docker/core/src/main/resources/persistence.properties.all b/docker/core/src/main/resources/persistence.properties.all
index 42f7758..583c689 100644
--- a/docker/core/src/main/resources/persistence.properties.all
+++ b/docker/core/src/main/resources/persistence.properties.all
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainSchemaDAO
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.JPAAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAAnyObjectDAO
diff --git a/docker/core/src/main/resources/persistence.properties.myjson b/docker/core/src/main/resources/persistence.properties.myjson
index cac21af..59be9e3 100644
--- a/docker/core/src/main/resources/persistence.properties.myjson
+++ b/docker/core/src/main/resources/persistence.properties.myjson
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainSchemaDA
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.MyJPAJSONAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONAnyObjectDAO
diff --git a/docker/core/src/main/resources/persistence.properties.pgjsonb b/docker/core/src/main/resources/persistence.properties.pgjsonb
index b88b052..c13cac9 100644
--- a/docker/core/src/main/resources/persistence.properties.pgjsonb
+++ b/docker/core/src/main/resources/persistence.properties.pgjsonb
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainSchemaDA
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.PGJPAJSONAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONAnyObjectDAO
diff --git a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
index 00e41e5..92cb77a 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
+++ b/ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java
@@ -24,6 +24,7 @@ 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 java.util.stream.Stream;
@@ -38,7 +39,7 @@ import org.apache.syncope.common.lib.types.ClientExceptionType;
 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.AssignableCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.DynRealmCond;
 import org.apache.syncope.core.persistence.api.dao.search.MemberCond;
 import org.apache.syncope.core.persistence.api.dao.search.MembershipCond;
@@ -213,59 +214,107 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
         return ArrayUtils.isEmpty(esResult)
                 ? List.of()
                 : buildResult(Stream.of(Objects.requireNonNull(esResult))
-                    .map(SearchHit::getId).collect(Collectors.toList()), kind);
+                        .map(SearchHit::getId).collect(Collectors.toList()), kind);
     }
 
     private QueryBuilder getQueryBuilder(final SearchCond cond, final AnyTypeKind kind) {
-        QueryBuilder builder = EMPTY_QUERY_BUILDER;
+        QueryBuilder builder = null;
 
         switch (cond.getType()) {
             case LEAF:
             case NOT_LEAF:
-                if (cond.getAnyTypeCond() != null && AnyTypeKind.ANY_OBJECT == kind) {
-                    builder = getQueryBuilder(cond.getAnyTypeCond());
-                } else if (cond.getRelationshipTypeCond() != null
-                        && (AnyTypeKind.USER == kind || AnyTypeKind.ANY_OBJECT == kind)) {
-
-                    builder = getQueryBuilder(cond.getRelationshipTypeCond());
-                } else if (cond.getRelationshipCond() != null
-                        && (AnyTypeKind.USER == kind || AnyTypeKind.ANY_OBJECT == kind)) {
-
-                    builder = getQueryBuilder(cond.getRelationshipCond());
-                } else if (cond.getMembershipCond() != null
-                        && (AnyTypeKind.USER == kind || AnyTypeKind.ANY_OBJECT == kind)) {
-
-                    builder = getQueryBuilder(cond.getMembershipCond());
-                } else if (cond.getAssignableCond() != null) {
-                    builder = getQueryBuilder(cond.getAssignableCond());
-                } else if (cond.getRoleCond() != null && AnyTypeKind.USER == kind) {
-                    builder = getQueryBuilder(cond.getRoleCond());
-                } else if (cond.getPrivilegeCond() != null && AnyTypeKind.USER == kind) {
-                    builder = getQueryBuilder(cond.getPrivilegeCond());
-                } else if (cond.getDynRealmCond() != null) {
-                    builder = getQueryBuilder(cond.getDynRealmCond());
-                } else if (cond.getMemberCond() != null && AnyTypeKind.GROUP == kind) {
-                    builder = getQueryBuilder(cond.getMemberCond());
-                } else if (cond.getResourceCond() != null) {
-                    builder = getQueryBuilder(cond.getResourceCond());
-                } else if (cond.getAttributeCond() != null) {
-                    builder = getQueryBuilder(cond.getAttributeCond(), kind);
-                } else if (cond.getAnyCond() != null) {
-                    builder = getQueryBuilder(cond.getAnyCond(), kind);
+                builder = cond.getLeaf(AnyTypeCond.class).
+                        filter(leaf -> AnyTypeKind.ANY_OBJECT == kind).
+                        map(leaf -> getQueryBuilder(leaf)).
+                        orElse(null);
+
+                if (builder == null) {
+                    builder = cond.getLeaf(RelationshipTypeCond.class).
+                            filter(leaf -> AnyTypeKind.GROUP != kind).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(RelationshipCond.class).
+                            filter(leaf -> AnyTypeKind.GROUP != kind).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(MembershipCond.class).
+                            filter(leaf -> AnyTypeKind.GROUP != kind).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(MemberCond.class).
+                            filter(leaf -> AnyTypeKind.GROUP == kind).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(AssignableCond.class).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(RoleCond.class).
+                            filter(leaf -> AnyTypeKind.USER == kind).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(PrivilegeCond.class).
+                            filter(leaf -> AnyTypeKind.USER == kind).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(DynRealmCond.class).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    builder = cond.getLeaf(ResourceCond.class).
+                            map(leaf -> getQueryBuilder(leaf)).
+                            orElse(null);
+                }
+
+                if (builder == null) {
+                    Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
+                    if (anyCond.isPresent()) {
+                        builder = getQueryBuilder(anyCond.get(), kind);
+                    } else {
+                        builder = cond.getLeaf(AttrCond.class).
+                                map(leaf -> getQueryBuilder(leaf, kind)).
+                                orElse(null);
+                    }
+                }
+
+                if (builder == null) {
+                    builder = EMPTY_QUERY_BUILDER;
                 }
                 builder = checkNot(builder, cond.getType() == SearchCond.Type.NOT_LEAF);
                 break;
 
             case AND:
                 builder = QueryBuilders.boolQuery().
-                        must(getQueryBuilder(cond.getLeftSearchCond(), kind)).
-                        must(getQueryBuilder(cond.getRightSearchCond(), kind));
+                        must(getQueryBuilder(cond.getLeft(), kind)).
+                        must(getQueryBuilder(cond.getRight(), kind));
                 break;
 
             case OR:
                 builder = QueryBuilders.disMaxQuery().
-                        add(getQueryBuilder(cond.getLeftSearchCond(), kind)).
-                        add(getQueryBuilder(cond.getRightSearchCond(), kind));
+                        add(getQueryBuilder(cond.getLeft(), kind)).
+                        add(getQueryBuilder(cond.getRight(), kind));
                 break;
 
             default:
@@ -360,9 +409,9 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
     }
 
     private static QueryBuilder fillAttrQuery(
-        final PlainSchema schema,
-        final PlainAttrValue attrValue,
-        final AttributeCond cond) {
+            final PlainSchema schema,
+            final PlainAttrValue attrValue,
+            final AttrCond cond) {
 
         Object value = schema.getType() == AttrSchemaType.Date && attrValue.getDateValue() != null
                 ? attrValue.getDateValue().getTime()
@@ -430,7 +479,7 @@ public class ElasticsearchAnySearchDAO extends AbstractAnySearchDAO {
         return builder;
     }
 
-    private QueryBuilder getQueryBuilder(final AttributeCond cond, final AnyTypeKind kind) {
+    private QueryBuilder getQueryBuilder(final AttrCond cond, final AnyTypeKind kind) {
         Pair<PlainSchema, PlainAttrValue> checked;
         try {
             checked = check(cond, kind);
diff --git a/ext/elasticsearch/persistence-jpa/src/main/resources/persistence.properties b/ext/elasticsearch/persistence-jpa/src/main/resources/persistence.properties
index 14b5143..82b0b51 100644
--- a/ext/elasticsearch/persistence-jpa/src/main/resources/persistence.properties
+++ b/ext/elasticsearch/persistence-jpa/src/main/resources/persistence.properties
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainSchemaDAO
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAAnyObjectDAO
diff --git a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java
index 50b9235..833edd6 100644
--- a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java
+++ b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestFormDirectoryPanel.java
@@ -30,7 +30,7 @@ import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.rest.UserRequestRestClient;
 import org.apache.syncope.client.console.panels.UserRequestFormDirectoryPanel.UserRequestFormProvider;
-import org.apache.syncope.client.console.layout.FormLayoutInfoUtils;
+import org.apache.syncope.client.console.layout.AnyLayoutUtils;
 import org.apache.syncope.client.console.layout.UserFormLayoutInfo;
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
@@ -248,7 +248,7 @@ public class UserRequestFormDirectoryPanel
                         previousUserTO,
                         newUserTO,
                         AnyTypeRestClient.read(AnyTypeKind.USER.name()).getClasses(),
-                        FormLayoutInfoUtils.fetch(List.of(AnyTypeKind.USER.name())).getLeft(),
+                        AnyLayoutUtils.fetch(List.of(AnyTypeKind.USER.name())).getUser(),
                         pageRef
                 ).build(BaseModal.CONTENT_ID, AjaxWizard.Mode.EDIT));
 
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
index 9ea68b2..e5f837f 100644
--- a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
@@ -631,7 +631,7 @@ public class SCIMDataBinder {
 
         MembershipCond membCond = new MembershipCond();
         membCond.setGroup(groupTO.getKey());
-        SearchCond searchCond = SearchCond.getLeafCond(membCond);
+        SearchCond searchCond = SearchCond.getLeaf(membCond);
 
         if (output(attributes, excludedAttributes, "members")) {
             int count = userLogic.search(searchCond,
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java
index 1d4eb95..926ba0a 100644
--- a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondConverter.java
@@ -34,8 +34,8 @@ public final class SearchCondConverter {
     private static final Logger LOG = LoggerFactory.getLogger(SearchCondConverter.class);
 
     public static SearchCond convert(final SearchCondVisitor visitor, final String filter) {
-        SCIMFilterParser parser = new SCIMFilterParser(new CommonTokenStream(
-                new SCIMFilterLexer(CharStreams.fromString(filter))));
+        SCIMFilterParser parser = new SCIMFilterParser(
+                new CommonTokenStream(new SCIMFilterLexer(CharStreams.fromString(filter))));
         parser.setBuildParseTree(true);
         parser.setTrimParseTree(true);
         parser.setProfile(true);
diff --git a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
index 872589a..890e208 100644
--- a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
+++ b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
@@ -28,7 +28,7 @@ import org.apache.syncope.common.lib.scim.SCIMConf;
 import org.apache.syncope.common.lib.scim.SCIMUserAddressConf;
 import org.apache.syncope.common.lib.scim.SCIMUserConf;
 import org.apache.syncope.core.persistence.api.dao.search.AnyCond;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+import org.apache.syncope.core.persistence.api.dao.search.AttrCond;
 import org.apache.syncope.core.persistence.api.dao.search.SearchCond;
 import org.apache.syncope.ext.scimv2.api.type.Resource;
 
@@ -55,29 +55,28 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
     }
 
     private static boolean schemaEquals(final Resource resource, final String value, final String schema) {
-        return Optional.ofNullable(resource)
-            .map(resource1 -> value.equalsIgnoreCase(schema)
-                || (resource1.schema() + ':' + value).equalsIgnoreCase(schema))
-            .orElseGet(() -> value.contains(":")
-            ? StringUtils.substringAfterLast(value, ":").equalsIgnoreCase(schema)
-            : value.equalsIgnoreCase(schema));
+        return resource == null
+                ? value.contains(":")
+                ? StringUtils.substringAfterLast(value, ":").equalsIgnoreCase(schema)
+                : value.equalsIgnoreCase(schema)
+                : value.equalsIgnoreCase(schema) || (resource.schema() + ":" + value).equalsIgnoreCase(schema);
     }
 
-    public AttributeCond createAttributeCond(final String schema) {
-        AttributeCond attributeCond = null;
+    public AttrCond createAttrCond(final String schema) {
+        AttrCond attrCond = null;
 
         if (schemaEquals(Resource.User, "userName", schema)) {
-            attributeCond = new AnyCond();
-            attributeCond.setSchema("username");
+            attrCond = new AnyCond();
+            attrCond.setSchema("username");
         } else if (resource == Resource.Group && schemaEquals(Resource.Group, "displayName", schema)) {
-            attributeCond = new AnyCond();
-            attributeCond.setSchema("name");
+            attrCond = new AnyCond();
+            attrCond.setSchema("name");
         } else if (schemaEquals(null, "meta.created", schema)) {
-            attributeCond = new AnyCond();
-            attributeCond.setSchema("creationDate");
+            attrCond = new AnyCond();
+            attrCond.setSchema("creationDate");
         } else if (schemaEquals(null, "meta.lastModified", schema)) {
-            attributeCond = new AnyCond();
-            attributeCond.setSchema("lastChangeDate");
+            attrCond = new AnyCond();
+            attrCond.setSchema("lastChangeDate");
         }
 
         if (resource == Resource.User) {
@@ -85,24 +84,24 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
                 if (conf.getUserConf().getName() != null) {
                     for (Map.Entry<String, String> entry : conf.getUserConf().getName().asMap().entrySet()) {
                         if (schemaEquals(Resource.User, "name." + entry.getKey(), schema)) {
-                            attributeCond = new AttributeCond();
-                            attributeCond.setSchema(entry.getValue());
+                            attrCond = new AttrCond();
+                            attrCond.setSchema(entry.getValue());
                         }
                     }
                 }
 
                 for (Map.Entry<String, String> entry : conf.getUserConf().asMap().entrySet()) {
                     if (schemaEquals(Resource.User, entry.getKey(), schema)) {
-                        attributeCond = new AttributeCond();
-                        attributeCond.setSchema(entry.getValue());
+                        attrCond = new AttrCond();
+                        attrCond.setSchema(entry.getValue());
                     }
                 }
 
                 for (SCIMUserAddressConf address : conf.getUserConf().getAddresses()) {
                     for (Map.Entry<String, String> entry : address.asMap().entrySet()) {
                         if (schemaEquals(Resource.User, "addresses." + entry.getKey(), schema)) {
-                            attributeCond = new AttributeCond();
-                            attributeCond.setSchema(entry.getValue());
+                            attrCond = new AttrCond();
+                            attrCond.setSchema(entry.getValue());
                         }
                     }
                 }
@@ -111,73 +110,73 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
             if (conf.getEnterpriseUserConf() != null) {
                 for (Map.Entry<String, String> entry : conf.getEnterpriseUserConf().asMap().entrySet()) {
                     if (schemaEquals(Resource.EnterpriseUser, entry.getKey(), schema)) {
-                        attributeCond = new AttributeCond();
-                        attributeCond.setSchema(entry.getValue());
+                        attrCond = new AttrCond();
+                        attrCond.setSchema(entry.getValue());
                     }
                 }
 
                 if (conf.getEnterpriseUserConf().getManager() != null
                         && conf.getEnterpriseUserConf().getManager().getKey() != null) {
 
-                    attributeCond = new AttributeCond();
-                    attributeCond.setSchema(conf.getEnterpriseUserConf().getManager().getKey());
+                    attrCond = new AttrCond();
+                    attrCond.setSchema(conf.getEnterpriseUserConf().getManager().getKey());
                 }
             }
         }
 
-        if (attributeCond == null) {
+        if (attrCond == null) {
             throw new IllegalArgumentException("Could not match " + schema + " for " + resource);
         }
 
-        return attributeCond;
+        return attrCond;
     }
 
-    private static SearchCond setOperator(final AttributeCond attributeCond, final String operator) {
+    private static SearchCond setOperator(final AttrCond attrCond, final String operator) {
         switch (operator) {
             case "eq":
             default:
-                attributeCond.setType(AttributeCond.Type.IEQ);
+                attrCond.setType(AttrCond.Type.IEQ);
                 break;
 
             case "ne":
-                attributeCond.setType(AttributeCond.Type.IEQ);
+                attrCond.setType(AttrCond.Type.IEQ);
                 break;
 
             case "sw":
-                attributeCond.setType(AttributeCond.Type.ILIKE);
-                attributeCond.setExpression(attributeCond.getExpression() + '%');
+                attrCond.setType(AttrCond.Type.ILIKE);
+                attrCond.setExpression(attrCond.getExpression() + "%");
                 break;
 
             case "co":
-                attributeCond.setType(AttributeCond.Type.ILIKE);
-                attributeCond.setExpression('%' + attributeCond.getExpression() + '%');
+                attrCond.setType(AttrCond.Type.ILIKE);
+                attrCond.setExpression("%" + attrCond.getExpression() + "%");
                 break;
 
             case "ew":
-                attributeCond.setType(AttributeCond.Type.ILIKE);
-                attributeCond.setExpression('%' + attributeCond.getExpression());
+                attrCond.setType(AttrCond.Type.ILIKE);
+                attrCond.setExpression("%" + attrCond.getExpression());
                 break;
 
             case "gt":
-                attributeCond.setType(AttributeCond.Type.GT);
+                attrCond.setType(AttrCond.Type.GT);
                 break;
 
             case "ge":
-                attributeCond.setType(AttributeCond.Type.GE);
+                attrCond.setType(AttrCond.Type.GE);
                 break;
 
             case "lt":
-                attributeCond.setType(AttributeCond.Type.LT);
+                attrCond.setType(AttrCond.Type.LT);
                 break;
 
             case "le":
-                attributeCond.setType(AttributeCond.Type.LE);
+                attrCond.setType(AttrCond.Type.LE);
                 break;
         }
 
         return "ne".equals(operator)
-                ? SearchCond.getNotLeafCond(attributeCond)
-                : SearchCond.getLeafCond(attributeCond);
+                ? SearchCond.getNotLeaf(attrCond)
+                : SearchCond.getLeaf(attrCond);
     }
 
     private <E extends Enum<?>> SearchCond complex(
@@ -187,23 +186,23 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
             Optional<SCIMComplexConf<E>> item = items.stream().
                     filter(object -> object.getType().name().equals(StringUtils.strip(right, "\""))).findFirst();
             if (item.isPresent()) {
-                AttributeCond attributeCond = new AttributeCond();
-                attributeCond.setSchema(item.get().getValue());
-                attributeCond.setType(AttributeCond.Type.ISNOTNULL);
-                return SearchCond.getLeafCond(attributeCond);
+                AttrCond attrCond = new AttrCond();
+                attrCond.setSchema(item.get().getValue());
+                attrCond.setType(AttrCond.Type.ISNOTNULL);
+                return SearchCond.getLeaf(attrCond);
             }
         } else if (!conf.getUserConf().getEmails().isEmpty()
                 && (MULTIVALUE.contains(left) || left.endsWith(".value"))) {
 
             List<SearchCond> orConds = new ArrayList<>();
             items.forEach(item -> {
-                AttributeCond cond = new AttributeCond();
+                AttrCond cond = new AttrCond();
                 cond.setSchema(item.getValue());
                 cond.setExpression(StringUtils.strip(right, "\""));
                 orConds.add(setOperator(cond, operator));
             });
             if (!orConds.isEmpty()) {
-                return SearchCond.getOrCond(orConds);
+                return SearchCond.getOr(orConds);
             }
         }
 
@@ -217,23 +216,23 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
             Optional<SCIMUserAddressConf> item = items.stream().
                     filter(object -> object.getType().name().equals(StringUtils.strip(right, "\""))).findFirst();
             if (item.isPresent()) {
-                AttributeCond attributeCond = new AttributeCond();
-                attributeCond.setSchema(item.get().getFormatted());
-                attributeCond.setType(AttributeCond.Type.ISNOTNULL);
-                return SearchCond.getLeafCond(attributeCond);
+                AttrCond attrCond = new AttrCond();
+                attrCond.setSchema(item.get().getFormatted());
+                attrCond.setType(AttrCond.Type.ISNOTNULL);
+                return SearchCond.getLeaf(attrCond);
             }
         } else if (!conf.getUserConf().getEmails().isEmpty()
                 && (MULTIVALUE.contains(left) || left.endsWith(".value"))) {
 
             List<SearchCond> orConds = new ArrayList<>();
             items.forEach(item -> {
-                AttributeCond cond = new AttributeCond();
+                AttrCond cond = new AttrCond();
                 cond.setSchema(item.getFormatted());
                 cond.setExpression(StringUtils.strip(right, "\""));
                 orConds.add(setOperator(cond, operator));
             });
             if (!orConds.isEmpty()) {
-                return SearchCond.getOrCond(orConds);
+                return SearchCond.getOr(orConds);
             }
         }
 
@@ -274,14 +273,14 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
         }
 
         if (result == null) {
-            AttributeCond attributeCond = createAttributeCond(left);
-            attributeCond.setExpression(StringUtils.strip(right, "\""));
-            result = setOperator(attributeCond, operator);
+            AttrCond attrCond = createAttrCond(left);
+            attrCond.setExpression(StringUtils.strip(right, "\""));
+            result = setOperator(attrCond, operator);
         }
 
         if (result == null) {
             throw new IllegalArgumentException(
-                    "Could not handle (" + left + ' ' + operator + ' ' + right + ") for " + resource);
+                    "Could not handle (" + left + " " + operator + " " + right + ") for " + resource);
         }
         return result;
     }
@@ -303,9 +302,9 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
 
     @Override
     public SearchCond visitATTR_PR(final SCIMFilterParser.ATTR_PRContext ctx) {
-        AttributeCond cond = createAttributeCond(ctx.ATTRNAME().getText());
-        cond.setType(AttributeCond.Type.ISNOTNULL);
-        return SearchCond.getLeafCond(cond);
+        AttrCond cond = createAttrCond(ctx.ATTRNAME().getText());
+        cond.setType(AttrCond.Type.ISNOTNULL);
+        return SearchCond.getLeaf(cond);
     }
 
     @Override
@@ -316,20 +315,24 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
     @Override
     public SearchCond visitNOT_EXPR(final SCIMFilterParser.NOT_EXPRContext ctx) {
         SearchCond cond = visit(ctx.expression());
-        if (cond.getAttributeCond() != null) {
-            if (cond.getAttributeCond().getType() == AttributeCond.Type.ISNULL) {
-                cond.getAttributeCond().setType(AttributeCond.Type.ISNOTNULL);
-            } else if (cond.getAttributeCond().getType() == AttributeCond.Type.ISNOTNULL) {
-                cond.getAttributeCond().setType(AttributeCond.Type.ISNULL);
-            }
-        } else if (cond.getAnyCond() != null) {
-            if (cond.getAnyCond().getType() == AnyCond.Type.ISNULL) {
-                cond.getAnyCond().setType(AnyCond.Type.ISNOTNULL);
-            } else if (cond.getAnyCond().getType() == AnyCond.Type.ISNOTNULL) {
-                cond.getAnyCond().setType(AnyCond.Type.ISNULL);
+        Optional<AnyCond> anyCond = cond.getLeaf(AnyCond.class);
+        if (anyCond.isPresent()) {
+            if (anyCond.get().getType() == AttrCond.Type.ISNULL) {
+                anyCond.get().setType(AttrCond.Type.ISNOTNULL);
+            } else if (anyCond.get().getType() == AttrCond.Type.ISNOTNULL) {
+                anyCond.get().setType(AttrCond.Type.ISNULL);
             }
         } else {
-            cond = SearchCond.getNotLeafCond(cond);
+            Optional<AttrCond> attrCond = cond.getLeaf(AttrCond.class);
+            if (attrCond.isPresent()) {
+                if (attrCond.get().getType() == AnyCond.Type.ISNULL) {
+                    attrCond.get().setType(AnyCond.Type.ISNOTNULL);
+                } else if (attrCond.get().getType() == AnyCond.Type.ISNOTNULL) {
+                    attrCond.get().setType(AnyCond.Type.ISNULL);
+                }
+            } else {
+                cond = SearchCond.getNotLeaf(cond);
+            }
         }
 
         return cond;
@@ -337,12 +340,11 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> {
 
     @Override
     public SearchCond visitEXPR_AND_EXPR(final SCIMFilterParser.EXPR_AND_EXPRContext ctx) {
-        return SearchCond.getAndCond(visit(ctx.expression(0)), visit(ctx.expression(1)));
+        return SearchCond.getAnd(visit(ctx.expression(0)), visit(ctx.expression(1)));
     }
 
     @Override
     public SearchCond visitEXPR_OR_EXPR(final SCIMFilterParser.EXPR_OR_EXPRContext ctx) {
-        return SearchCond.getOrCond(visit(ctx.expression(0)), visit(ctx.expression(1)));
+        return SearchCond.getOr(visit(ctx.expression(0)), visit(ctx.expression(1)));
     }
-
 }
diff --git a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java
index 1082869..d29d383 100644
--- a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java
+++ b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java
@@ -21,13 +21,15 @@ package org.apache.syncope.core.logic.scim;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.apache.syncope.common.lib.scim.SCIMComplexConf;
 import org.apache.syncope.common.lib.scim.SCIMConf;
 import org.apache.syncope.common.lib.scim.SCIMUserConf;
 import org.apache.syncope.common.lib.scim.SCIMUserNameConf;
 import org.apache.syncope.common.lib.scim.types.EmailCanonicalType;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+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.ext.scimv2.api.type.Resource;
 import org.junit.jupiter.api.BeforeAll;
@@ -64,20 +66,20 @@ public class SCIMFilterTest {
     public void eq() {
         SearchCond cond = SearchCondConverter.convert(VISITOR, "userName eq \"bjensen\"");
         assertNotNull(cond);
-        assertNotNull(cond.getAnyCond());
-        assertEquals("username", cond.getAnyCond().getSchema());
-        assertEquals(AttributeCond.Type.IEQ, cond.getAnyCond().getType());
-        assertEquals("bjensen", cond.getAnyCond().getExpression());
+        assertTrue(cond.getLeaf(AnyCond.class).isPresent());
+        assertEquals("username", cond.getLeaf(AnyCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.IEQ, cond.getLeaf(AnyCond.class).get().getType());
+        assertEquals("bjensen", cond.getLeaf(AnyCond.class).get().getExpression());
     }
 
     @Test
     public void sw() {
         SearchCond cond = SearchCondConverter.convert(VISITOR, "userName sw \"J\"");
         assertNotNull(cond);
-        assertNotNull(cond.getAnyCond());
-        assertEquals("username", cond.getAnyCond().getSchema());
-        assertEquals(AttributeCond.Type.ILIKE, cond.getAnyCond().getType());
-        assertEquals("J%", cond.getAnyCond().getExpression());
+        assertTrue(cond.getLeaf(AnyCond.class).isPresent());
+        assertEquals("username", cond.getLeaf(AnyCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ILIKE, cond.getLeaf(AnyCond.class).get().getType());
+        assertEquals("J%", cond.getLeaf(AnyCond.class).get().getExpression());
 
         SearchCond fqn = SearchCondConverter.convert(
                 VISITOR, "urn:ietf:params:scim:schemas:core:2.0:User:userName sw \"J\"");
@@ -88,30 +90,30 @@ public class SCIMFilterTest {
     public void pr() {
         SearchCond cond = SearchCondConverter.convert(VISITOR, "title pr");
         assertNotNull(cond);
-        assertNotNull(cond.getAttributeCond());
-        assertEquals("title", cond.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ISNOTNULL, cond.getAttributeCond().getType());
-        assertNull(cond.getAttributeCond().getExpression());
+        assertTrue(cond.getLeaf(AttrCond.class).isPresent());
+        assertEquals("title", cond.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ISNOTNULL, cond.getLeaf(AttrCond.class).get().getType());
+        assertNull(cond.getLeaf(AttrCond.class).get().getExpression());
     }
 
     @Test
     public void gt() {
         SearchCond cond = SearchCondConverter.convert(VISITOR, "meta.lastModified gt \"2011-05-13T04:42:34Z\"");
         assertNotNull(cond);
-        assertNotNull(cond.getAnyCond());
-        assertEquals("lastChangeDate", cond.getAnyCond().getSchema());
-        assertEquals(AttributeCond.Type.GT, cond.getAnyCond().getType());
-        assertEquals("2011-05-13T04:42:34Z", cond.getAnyCond().getExpression());
+        assertTrue(cond.getLeaf(AnyCond.class).isPresent());
+        assertEquals("lastChangeDate", cond.getLeaf(AnyCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.GT, cond.getLeaf(AnyCond.class).get().getType());
+        assertEquals("2011-05-13T04:42:34Z", cond.getLeaf(AnyCond.class).get().getExpression());
     }
 
     @Test
     public void not() {
         SearchCond cond = SearchCondConverter.convert(VISITOR, "not (title pr)");
         assertNotNull(cond);
-        assertNotNull(cond.getAttributeCond());
-        assertEquals("title", cond.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ISNULL, cond.getAttributeCond().getType());
-        assertNull(cond.getAttributeCond().getExpression());
+        assertTrue(cond.getLeaf(AttrCond.class).isPresent());
+        assertEquals("title", cond.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ISNULL, cond.getLeaf(AttrCond.class).get().getType());
+        assertNull(cond.getLeaf(AttrCond.class).get().getExpression());
     }
 
     @Test
@@ -120,19 +122,19 @@ public class SCIMFilterTest {
         assertNotNull(cond);
         assertEquals(SearchCond.Type.AND, cond.getType());
 
-        SearchCond left = cond.getLeftSearchCond();
+        SearchCond left = cond.getLeft();
         assertNotNull(left);
-        assertNotNull(left.getAttributeCond());
-        assertEquals("title", left.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ISNOTNULL, left.getAttributeCond().getType());
-        assertNull(left.getAttributeCond().getExpression());
+        assertTrue(left.getLeaf(AttrCond.class).isPresent());
+        assertEquals("title", left.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ISNOTNULL, left.getLeaf(AttrCond.class).get().getType());
+        assertNull(left.getLeaf(AttrCond.class).get().getExpression());
 
-        SearchCond right = cond.getRightSearchCond();
+        SearchCond right = cond.getRight();
         assertNotNull(right);
-        assertNotNull(right.getAnyCond());
-        assertEquals("username", right.getAnyCond().getSchema());
-        assertEquals(AttributeCond.Type.ILIKE, right.getAnyCond().getType());
-        assertEquals("J%", right.getAnyCond().getExpression());
+        assertTrue(right.getLeaf(AnyCond.class).isPresent());
+        assertEquals("username", right.getLeaf(AnyCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ILIKE, right.getLeaf(AnyCond.class).get().getType());
+        assertEquals("J%", right.getLeaf(AnyCond.class).get().getExpression());
     }
 
     @Test
@@ -141,19 +143,19 @@ public class SCIMFilterTest {
         assertNotNull(cond);
         assertEquals(SearchCond.Type.OR, cond.getType());
 
-        SearchCond left = cond.getLeftSearchCond();
+        SearchCond left = cond.getLeft();
         assertNotNull(left);
-        assertNotNull(left.getAttributeCond());
-        assertEquals("title", left.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ISNOTNULL, left.getAttributeCond().getType());
-        assertNull(left.getAttributeCond().getExpression());
+        assertTrue(left.getLeaf(AttrCond.class).isPresent());
+        assertEquals("title", left.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ISNOTNULL, left.getLeaf(AttrCond.class).get().getType());
+        assertNull(left.getLeaf(AttrCond.class).get().getExpression());
 
-        SearchCond right = cond.getRightSearchCond();
+        SearchCond right = cond.getRight();
         assertNotNull(right);
-        assertNotNull(right.getAttributeCond());
-        assertEquals("cn", right.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.IEQ, right.getAttributeCond().getType());
-        assertEquals("Other", right.getAttributeCond().getExpression());
+        assertTrue(right.getLeaf(AttrCond.class).isPresent());
+        assertEquals("cn", right.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.IEQ, right.getLeaf(AttrCond.class).get().getType());
+        assertEquals("Other", right.getLeaf(AttrCond.class).get().getExpression());
     }
 
     @Test
@@ -162,18 +164,18 @@ public class SCIMFilterTest {
         assertNotNull(cond);
         assertEquals(SearchCond.Type.AND, cond.getType());
 
-        SearchCond left = cond.getLeftSearchCond();
+        SearchCond left = cond.getLeft();
         assertNotNull(left);
-        assertNotNull(left.getAttributeCond());
-        assertEquals("userType", left.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.IEQ, left.getAttributeCond().getType());
-        assertEquals("Employee", left.getAttributeCond().getExpression());
+        assertTrue(left.getLeaf(AttrCond.class).isPresent());
+        assertEquals("userType", left.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.IEQ, left.getLeaf(AttrCond.class).get().getType());
+        assertEquals("Employee", left.getLeaf(AttrCond.class).get().getExpression());
 
-        SearchCond right = cond.getRightSearchCond();
+        SearchCond right = cond.getRight();
         assertNotNull(right);
-        assertNotNull(right.getAttributeCond());
-        assertEquals("email", right.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ISNOTNULL, right.getAttributeCond().getType());
+        assertTrue(right.getLeaf(AttrCond.class).isPresent());
+        assertEquals("email", right.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ISNOTNULL, right.getLeaf(AttrCond.class).get().getType());
     }
 
     @Test
@@ -182,10 +184,10 @@ public class SCIMFilterTest {
         assertNotNull(cond);
         assertEquals(SearchCond.Type.LEAF, cond.getType());
 
-        AttributeCond leaf = cond.getAttributeCond();
+        AttrCond leaf = cond.getLeaf(AttrCond.class).get();
         assertNotNull(leaf);
         assertEquals("surname", leaf.getSchema());
-        assertEquals(AttributeCond.Type.ILIKE, leaf.getType());
+        assertEquals(AttrCond.Type.ILIKE, leaf.getType());
         assertEquals("%O'Malley%", leaf.getExpression());
     }
 
@@ -196,40 +198,40 @@ public class SCIMFilterTest {
         assertNotNull(cond);
         assertEquals(SearchCond.Type.OR, cond.getType());
 
-        SearchCond left = cond.getLeftSearchCond();
+        SearchCond left = cond.getLeft();
         assertNotNull(left);
         assertEquals(SearchCond.Type.OR, left.getType());
 
-        SearchCond left1 = left.getLeftSearchCond();
+        SearchCond left1 = left.getLeft();
         assertNotNull(left1);
-        assertNotNull(left1.getAttributeCond());
-        assertEquals("email", left1.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ILIKE, left1.getAttributeCond().getType());
-        assertEquals("%example.com%", left1.getAttributeCond().getExpression());
+        assertTrue(left1.getLeaf(AttrCond.class).isPresent());
+        assertEquals("email", left1.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ILIKE, left1.getLeaf(AttrCond.class).get().getType());
+        assertEquals("%example.com%", left1.getLeaf(AttrCond.class).get().getExpression());
 
-        SearchCond left2 = left.getRightSearchCond();
+        SearchCond left2 = left.getRight();
         assertNotNull(left2);
-        assertNotNull(left2.getAttributeCond());
-        assertEquals("gmail", left2.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ILIKE, left2.getAttributeCond().getType());
-        assertEquals("%example.com%", left2.getAttributeCond().getExpression());
+        assertTrue(left2.getLeaf(AttrCond.class).isPresent());
+        assertEquals("gmail", left2.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ILIKE, left2.getLeaf(AttrCond.class).get().getType());
+        assertEquals("%example.com%", left2.getLeaf(AttrCond.class).get().getExpression());
 
-        SearchCond right = cond.getRightSearchCond();
+        SearchCond right = cond.getRight();
         assertNotNull(right);
         assertEquals(SearchCond.Type.OR, right.getType());
 
-        SearchCond right1 = right.getLeftSearchCond();
+        SearchCond right1 = right.getLeft();
         assertNotNull(right1);
-        assertNotNull(right1.getAttributeCond());
-        assertEquals("email", right1.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ILIKE, right1.getAttributeCond().getType());
-        assertEquals("%example.org%", right1.getAttributeCond().getExpression());
+        assertTrue(right1.getLeaf(AttrCond.class).isPresent());
+        assertEquals("email", right1.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ILIKE, right1.getLeaf(AttrCond.class).get().getType());
+        assertEquals("%example.org%", right1.getLeaf(AttrCond.class).get().getExpression());
 
-        SearchCond right2 = right.getRightSearchCond();
+        SearchCond right2 = right.getRight();
         assertNotNull(right2);
-        assertNotNull(right2.getAttributeCond());
-        assertEquals("gmail", right2.getAttributeCond().getSchema());
-        assertEquals(AttributeCond.Type.ILIKE, right2.getAttributeCond().getType());
-        assertEquals("%example.org%", right2.getAttributeCond().getExpression());
+        assertTrue(right2.getLeaf(AttrCond.class).isPresent());
+        assertEquals("gmail", right2.getLeaf(AttrCond.class).get().getSchema());
+        assertEquals(AttrCond.Type.ILIKE, right2.getLeaf(AttrCond.class).get().getType());
+        assertEquals("%example.org%", right2.getLeaf(AttrCond.class).get().getExpression());
     }
 }
diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java
index e9e32aa..72222a5 100644
--- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java
+++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractService.java
@@ -205,7 +205,7 @@ abstract class AbstractService<R extends SCIMResource> {
             sort = List.of();
         } else {
             OrderByClause clause = new OrderByClause();
-            clause.setField(visitor.createAttributeCond(request.getSortBy()).getSchema());
+            clause.setField(visitor.createAttrCond(request.getSortBy()).getSchema());
             clause.setDirection(request.getSortOrder() == null || request.getSortOrder() == SortOrder.ascending
                     ? OrderByClause.Direction.ASC
                     : OrderByClause.Direction.DESC);
diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java
index 5e6a1a2..4ef3c11 100644
--- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java
+++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/GroupServiceImpl.java
@@ -109,7 +109,7 @@ public class GroupServiceImpl extends AbstractService<SCIMGroup> implements Grou
 
         MembershipCond membCond = new MembershipCond();
         membCond.setGroup(id);
-        SearchCond searchCond = SearchCond.getLeafCond(membCond);
+        SearchCond searchCond = SearchCond.getLeaf(membCond);
         int count = userLogic().search(searchCond,
                 1, 1, List.of(),
                 SyncopeConstants.ROOT_REALM, false).getLeft();
diff --git a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java
index 7761183..ae56169 100644
--- a/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java
+++ b/fit/core-reference/src/main/java-all/org/apache/syncope/fit/core/reference/flowable/PrintersValueProvider.java
@@ -43,7 +43,7 @@ public class PrintersValueProvider implements DropdownValueProvider {
     static {
         AnyTypeCond anyTypeCond = new AnyTypeCond();
         anyTypeCond.setAnyTypeKey("PRINTER");
-        PRINTER_COND = SearchCond.getLeafCond(anyTypeCond);
+        PRINTER_COND = SearchCond.getLeaf(anyTypeCond);
 
         OrderByClause orderByNameAsc = new OrderByClause();
         orderByNameAsc.setField("name");
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
index fece58b..379d305 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/CustomJWTSSOProvider.java
@@ -29,7 +29,7 @@ import org.apache.cxf.rs.security.jose.jws.JwsVerificationSignature;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
 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.search.AttributeCond;
+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.user.User;
 import org.apache.syncope.core.spring.security.AuthDataAccessor;
@@ -82,12 +82,12 @@ public class CustomJWTSSOProvider implements JWTSSOProvider {
     @Transactional(readOnly = true)
     @Override
     public Pair<User, Set<SyncopeGrantedAuthority>> resolve(final JwtClaims jwtClaims) {
-        AttributeCond userIdCond = new AttributeCond();
+        AttrCond userIdCond = new AttrCond();
         userIdCond.setSchema("userId");
-        userIdCond.setType(AttributeCond.Type.EQ);
+        userIdCond.setType(AttrCond.Type.EQ);
         userIdCond.setExpression(jwtClaims.getSubject());
 
-        List<User> matching = searchDAO.search(SearchCond.getLeafCond(userIdCond), AnyTypeKind.USER);
+        List<User> matching = searchDAO.search(SearchCond.getLeaf(userIdCond), AnyTypeKind.USER);
         if (matching.size() == 1) {
             User user = matching.get(0);
             Set<SyncopeGrantedAuthority> authorities = authDataAccessor.getAuthorities(user.getUsername());
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRule.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRule.java
index 0e628e9..3c0b86f 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRule.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/LinkedAccountSamplePullCorrelationRule.java
@@ -24,7 +24,7 @@ import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
 import org.apache.syncope.core.persistence.api.dao.PullCorrelationRuleConfClass;
 import org.apache.syncope.core.persistence.api.dao.PullMatch;
 import org.apache.syncope.core.persistence.api.dao.UserDAO;
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+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.resource.Provision;
@@ -44,18 +44,18 @@ public class LinkedAccountSamplePullCorrelationRule implements PullCorrelationRu
 
     @Override
     public SearchCond getSearchCond(final SyncDelta syncDelta, final Provision provision) {
-        AttributeCond cond = new AttributeCond();
+        AttrCond cond = new AttrCond();
 
         Attribute email = syncDelta.getObject().getAttributeByName("email");
         if (email != null && !CollectionUtils.isEmpty(email.getValue())) {
             cond.setSchema("email");
-            cond.setType(AttributeCond.Type.EQ);
+            cond.setType(AttrCond.Type.EQ);
             cond.setExpression(email.getValue().get(0).toString());
         } else {
             cond.setSchema("");
         }
 
-        return SearchCond.getLeafCond(cond);
+        return SearchCond.getLeaf(cond);
     }
 
     @Transactional(readOnly = true)
diff --git a/fit/core-reference/src/main/resources/elasticsearch/persistence.properties b/fit/core-reference/src/main/resources/elasticsearch/persistence.properties
index 14b5143..82b0b51 100644
--- a/fit/core-reference/src/main/resources/elasticsearch/persistence.properties
+++ b/fit/core-reference/src/main/resources/elasticsearch/persistence.properties
@@ -20,6 +20,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainSchemaDAO
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.ElasticsearchAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAAnyObjectDAO
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 5f0c224..6711c74 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
@@ -116,18 +116,18 @@ import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.syncope.common.rest.api.service.TaskService;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.syncope.common.rest.api.service.UserService;
-import org.apache.syncope.fit.core.UserITCase;
-import org.identityconnectors.common.security.Encryptor;
-import org.junit.jupiter.api.BeforeAll;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.apache.syncope.common.rest.api.service.UserRequestService;
 import org.apache.syncope.common.rest.api.service.BpmnProcessService;
 import org.apache.syncope.common.rest.api.service.GatewayRouteService;
 import org.apache.syncope.common.rest.api.service.UserWorkflowTaskService;
 import org.apache.syncope.fit.core.CoreITContext;
+import org.apache.syncope.fit.core.UserITCase;
+import org.identityconnectors.common.security.Encryptor;
+import org.junit.jupiter.api.BeforeAll;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
 
 @SpringJUnitConfig({ CoreITContext.class, SelfKeymasterClientContext.class, ZookeeperKeymasterClientContext.class })
diff --git a/fit/core-reference/src/test/resources/TestPullRule.groovy b/fit/core-reference/src/test/resources/TestPullRule.groovy
index 222480e..47d4807 100644
--- a/fit/core-reference/src/test/resources/TestPullRule.groovy
+++ b/fit/core-reference/src/test/resources/TestPullRule.groovy
@@ -19,7 +19,7 @@
  */
 import groovy.transform.CompileStatic
 import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule
-import org.apache.syncope.core.persistence.api.dao.search.AttributeCond;
+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.resource.Provision
 import org.identityconnectors.framework.common.objects.SyncDelta
@@ -32,11 +32,11 @@ class TestPullRule implements PullCorrelationRule {
 
   @Override
   SearchCond getSearchCond(final SyncDelta syncDelta, final Provision provision) {
-    AttributeCond cond = new AttributeCond();
+    AttrCond cond = new AttrCond();
     cond.setSchema("email");
-    cond.setType(AttributeCond.Type.EQ);
+    cond.setType(AttrCond.Type.EQ);
     cond.setExpression(syncDelta.getObject().getName().getNameValue());
 
-    return SearchCond.getLeafCond(cond);
+    return SearchCond.getLeaf(cond);
   }
 }
diff --git a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/dbms.adoc b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/dbms.adoc
index 6048c2f..30f102e 100644
--- a/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/dbms.adoc
+++ b/src/main/asciidoc/reference-guide/workingwithapachesyncope/systemadministration/dbms.adoc
@@ -87,6 +87,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainSchemaDA
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.PGJPAJSONAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONAnyObjectDAO
@@ -219,6 +220,7 @@ plainSchema.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainSchemaDA
 plainAttr.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrDAO
 plainAttrValue.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONPlainAttrValueDAO
 any.search.dao=org.apache.syncope.core.persistence.jpa.dao.MyJPAJSONAnySearchDAO
+any.search.visitor=org.apache.syncope.core.persistence.api.search.SearchCondVisitor
 user.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONUserDAO
 group.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONGroupDAO
 anyObject.dao=org.apache.syncope.core.persistence.jpa.dao.JPAJSONAnyObjectDAO