You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by fm...@apache.org on 2015/12/09 18:10:59 UTC

[2/2] syncope git commit: [SYNCOPE-156] providing dynamic membership

[SYNCOPE-156] providing dynamic membership


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/c50dae60
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/c50dae60
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/c50dae60

Branch: refs/heads/master
Commit: c50dae60a8404df618190c00ea57eed414ac3c66
Parents: 1e51b34
Author: fmartelli <fa...@gmail.com>
Authored: Wed Dec 9 18:10:19 2015 +0100
Committer: fmartelli <fa...@gmail.com>
Committed: Wed Dec 9 18:10:19 2015 +0100

----------------------------------------------------------------------
 .../syncope/client/console/panels/Realm.java    |   6 +-
 .../panels/search/AbstractSearchPanel.java      | 205 ++++++++
 .../panels/search/AnyObjectSearchPanel.java     |  92 ++++
 .../console/panels/search/GroupSearchPanel.java |  77 +++
 .../console/panels/search/MapOfListModel.java   |  68 +++
 .../console/panels/search/SearchClause.java     | 129 +++++
 .../panels/search/SearchClausePanel.java        | 488 +++++++++++++++++++
 .../console/panels/search/SearchUtils.java      | 266 ++++++++++
 .../console/panels/search/UserSearchPanel.java  |  46 ++
 .../wicket/markup/html/form/FieldPanel.java     |  21 +
 .../markup/html/form/MultiFieldPanel.java       |  52 +-
 .../console/wizards/AjaxWizardBuilder.java      |   8 +-
 .../console/wizards/AjaxWizardButton.java       |   2 +-
 .../console/wizards/AjaxWizardButtonBar.java    |  10 +-
 .../client/console/wizards/any/Details.java     |   2 +-
 .../console/wizards/any/GroupDetails.java       | 119 ++++-
 .../console/wizards/any/GroupWizardBuilder.java | 102 +++-
 .../META-INF/resources/css/fieldstyle.css       |  62 ---
 .../resources/META-INF/resources/css/search.css |  75 +++
 .../META-INF/resources/css/syncopeConsole.css   |   1 +
 .../syncope/client/console/pages/BasePage.html  |   2 +-
 .../panels/search/AbstractSearchPanel.html      |  36 ++
 .../panels/search/SearchClausePanel.html        |  32 ++
 .../markup/html/form/AjaxTextFieldPanel.html    |   2 +-
 .../console/wizards/any/GroupDetails.html       |  17 +-
 .../console/wizards/any/GroupDetails.properties |   4 +
 .../wizards/any/GroupDetails_it.properties      |   4 +
 .../wizards/any/GroupDetails_pt_BR.properties   |   4 +
 28 files changed, 1824 insertions(+), 108 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
index 626bcaa..3eb8c5b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/Realm.java
@@ -122,7 +122,7 @@ public class Realm extends Panel {
                         realmTO.getFullPath(),
                         anyTypeTO.getKey()).
                         addNewItemPanelBuilder(new UserWizardBuilder(
-                                        BaseModal.CONTENT_ID, userTO, anyTypeTO.getClasses(), pageRef)).
+                                BaseModal.CONTENT_ID, userTO, anyTypeTO.getClasses(), pageRef)).
                         addNotificationPanel(BasePage.class.cast(this.pageRef.getPage()).getFeedbackPanel()).
                         build(id);
                 break;
@@ -135,7 +135,7 @@ public class Realm extends Panel {
                         realmTO.getFullPath(),
                         anyTypeTO.getKey()).
                         addNewItemPanelBuilder(new GroupWizardBuilder(
-                                        BaseModal.CONTENT_ID, groupTO, anyTypeTO.getClasses(), pageRef)).
+                                BaseModal.CONTENT_ID, groupTO, anyTypeTO.getClasses(), pageRef)).
                         addNotificationPanel(BasePage.class.cast(this.pageRef.getPage()).getFeedbackPanel()).
                         build(id);
                 break;
@@ -149,7 +149,7 @@ public class Realm extends Panel {
                         realmTO.getFullPath(),
                         anyTypeTO.getKey()).
                         addNewItemPanelBuilder(new AnyWizardBuilder<AnyObjectTO>(
-                                        BaseModal.CONTENT_ID, anyObjectTO, anyTypeTO.getClasses(), pageRef)).
+                                BaseModal.CONTENT_ID, anyObjectTO, anyTypeTO.getClasses(), pageRef)).
                         addNotificationPanel(BasePage.class.cast(this.pageRef.getPage()).getFeedbackPanel()).
                         build(id);
                 break;

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java
new file mode 100644
index 0000000..90ff64c
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AbstractSearchPanel.java
@@ -0,0 +1,205 @@
+/*
+ * 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.panels.search;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.syncope.client.console.panels.NotificationPanel;
+import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.console.rest.SchemaRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
+import org.apache.syncope.common.lib.search.SearchableFields;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.feedback.FeedbackMessage;
+import org.apache.wicket.feedback.IFeedbackMessageFilter;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractSearchPanel extends Panel {
+
+    private static final long serialVersionUID = 5922413053568696414L;
+
+    /**
+     * Logger.
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractSearchPanel.class);
+
+    protected SchemaRestClient schemaRestClient = new SchemaRestClient();
+
+    protected ResourceRestClient resourceRestClient = new ResourceRestClient();
+
+//    protected AuthRestClient authRestClient;
+    protected IModel<List<String>> dnames;
+
+    protected IModel<List<String>> anames;
+
+    protected IModel<List<String>> resourceNames;
+
+//    protected IModel<List<String>> entitlements;
+    protected IModel<List<SearchClause.Type>> types;
+
+    protected IModel<List<String>> groupNames;
+
+    protected NotificationPanel searchFeedback;
+
+    protected PropertyModel<List<SearchClause>> model;
+
+    protected WebMarkupContainer searchFormContainer;
+
+    protected AnyTypeKind typeKind;
+
+    protected boolean required;
+
+    public abstract static class Builder<T extends AbstractSearchPanel> implements Serializable {
+
+        private static final long serialVersionUID = 6308997285778809578L;
+
+        protected final PropertyModel<List<SearchClause>> model;
+
+        protected boolean required = true;
+
+        public Builder(final PropertyModel<List<SearchClause>> model) {
+            this.model = model;
+        }
+
+        public Builder<T> required(final boolean required) {
+            this.required = required;
+            return this;
+        }
+
+        public abstract T build(final String id);
+    }
+
+    protected AbstractSearchPanel(
+            final String id,
+            final PropertyModel<List<SearchClause>> model,
+            final AnyTypeKind typeKind) {
+        this(id, model, typeKind, true);
+    }
+
+    protected AbstractSearchPanel(
+            final String id,
+            final PropertyModel<List<SearchClause>> model,
+            final AnyTypeKind typeKind,
+            final boolean required) {
+
+        super(id);
+        populate();
+
+        this.typeKind = typeKind;
+        this.required = required;
+
+        setOutputMarkupId(true);
+
+        searchFormContainer = new WebMarkupContainer("searchFormContainer");
+        searchFormContainer.setOutputMarkupId(true);
+        add(searchFormContainer);
+
+        searchFeedback = new NotificationPanel("searchFeedback", new IFeedbackMessageFilter() {
+
+            private static final long serialVersionUID = 6895024863321391672L;
+
+            @Override
+            public boolean accept(final FeedbackMessage message) {
+                boolean result;
+
+                // messages reported on the session have a null reporter
+                if (message.getReporter() == null) {
+                    result = false;
+                } else {
+                    // only accept messages coming from the children of the search form container
+                    result = searchFormContainer.contains(message.getReporter(), true);
+                }
+
+                return result;
+            }
+        });
+        searchFeedback.setOutputMarkupId(true);
+        add(searchFeedback);
+
+        final SearchClausePanel searchClausePanel = new SearchClausePanel("panel", "panel",
+                Model.of(new SearchClause()),
+                required,
+                types, anames, dnames, groupNames, resourceNames);
+
+        final MultiFieldPanel.Builder<SearchClause> searchView = new MultiFieldPanel.Builder<SearchClause>(model) {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected SearchClause newModelObject() {
+                return new SearchClause();
+            }
+        };
+
+        searchFormContainer.add(searchView.build("search", "search", searchClausePanel).hideLabel());
+    }
+
+    protected void populate() {
+        dnames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                return SearchableFields.get(typeKind);
+            }
+        };
+
+        anames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                return schemaRestClient.getPlainSchemaNames();
+            }
+        };
+
+        resourceNames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                return CollectionUtils.collect(resourceRestClient.getAll(), new Transformer<ResourceTO, String>() {
+
+                    @Override
+                    public String transform(final ResourceTO input) {
+                        return input.getKey();
+                    }
+                }, new ArrayList<String>());
+            }
+        };
+    }
+
+    public NotificationPanel getSearchFeedback() {
+        return searchFeedback;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java
new file mode 100644
index 0000000..16115b0
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/AnyObjectSearchPanel.java
@@ -0,0 +1,92 @@
+/*
+ * 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.panels.search;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.syncope.client.console.rest.GroupRestClient;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.PropertyModel;
+
+public class AnyObjectSearchPanel extends AbstractSearchPanel {
+
+    private static final long serialVersionUID = -1769527800450203738L;
+
+    private final GroupRestClient groupRestClient = new GroupRestClient();
+
+    public static class Builder extends AbstractSearchPanel.Builder<AnyObjectSearchPanel> {
+
+        private static final long serialVersionUID = 6308997285778809578L;
+
+        public Builder(final PropertyModel<List<SearchClause>> model) {
+            super(model);
+        }
+
+        @Override
+        public AnyObjectSearchPanel build(final String id) {
+            return new AnyObjectSearchPanel(id, AnyTypeKind.ANY_OBJECT, this);
+        }
+    }
+
+    protected AnyObjectSearchPanel(final String id, final AnyTypeKind kind, final Builder builder) {
+        super(id, builder.model, kind, builder.required);
+    }
+
+    @Override
+    protected void populate() {
+        super.populate();
+
+        this.types = new LoadableDetachableModel<List<SearchClause.Type>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<SearchClause.Type> load() {
+                List<SearchClause.Type> result = new ArrayList<SearchClause.Type>();
+                result.add(SearchClause.Type.ATTRIBUTE);
+                result.add(SearchClause.Type.MEMBERSHIP);
+                result.add(SearchClause.Type.RESOURCE);
+                return result;
+            }
+        };
+
+        this.groupNames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                List<GroupTO> groupTOs = groupRestClient.list("/",
+                        -1, -1,
+                        new SortParam<>("name", true),
+                        null);
+
+                List<String> result = new ArrayList<>(groupTOs.size());
+                for (GroupTO group : groupTOs) {
+                    result.add(group.getName());
+                }
+
+                return result;
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
new file mode 100644
index 0000000..046e00f
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/GroupSearchPanel.java
@@ -0,0 +1,77 @@
+/*
+ * 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.panels.search;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.PropertyModel;
+
+public final class GroupSearchPanel extends AbstractSearchPanel {
+
+    private static final long serialVersionUID = 5757183539269316263L;
+
+    public static class Builder extends AbstractSearchPanel.Builder<GroupSearchPanel> {
+
+        private static final long serialVersionUID = 6308997285778809578L;
+
+        public Builder(final PropertyModel<List<SearchClause>> model) {
+            super(model);
+        }
+
+        @Override
+        public GroupSearchPanel build(final String id) {
+            return new GroupSearchPanel(id, this);
+        }
+    }
+
+    private GroupSearchPanel(final String id, final GroupSearchPanel.Builder builder) {
+        super(id, builder.model, AnyTypeKind.USER, builder.required);
+    }
+
+    @Override
+    protected void populate() {
+        super.populate();
+
+        this.types = new LoadableDetachableModel<List<SearchClause.Type>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<SearchClause.Type> load() {
+                final List<SearchClause.Type> result = new ArrayList<SearchClause.Type>();
+                result.add(SearchClause.Type.ATTRIBUTE);
+                result.add(SearchClause.Type.RESOURCE);
+                return result;
+            }
+        };
+
+        this.groupNames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                return Collections.<String>emptyList();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/MapOfListModel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/MapOfListModel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/MapOfListModel.java
new file mode 100644
index 0000000..403c498
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/MapOfListModel.java
@@ -0,0 +1,68 @@
+/*
+ * 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.panels.search;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.wicket.core.util.lang.PropertyResolver;
+import org.apache.wicket.model.PropertyModel;
+
+public class MapOfListModel<T> extends PropertyModel<List<T>> {
+
+    private static final long serialVersionUID = -7647997536634092231L;
+
+    private final String key;
+
+    public MapOfListModel(final Object modelObject, final String expression, final String key) {
+        super(modelObject, expression);
+        this.key = key;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<T> getObject() {
+        final String expression = propertyExpression();
+        final Object target = getInnermostModelOrObject();
+
+        if (target == null || StringUtils.isBlank(expression) || expression.startsWith(".")) {
+            throw new IllegalArgumentException("Property expressions cannot start with a '.' character");
+        }
+
+        final Map<String, List<T>> map = (Map<String, List<T>>) PropertyResolver.getValue(expression, target);
+
+        final List<T> res;
+        if (map.containsKey(key)) {
+            res = map.get(key);
+        } else {
+            res = new ArrayList<T>();
+            map.put(key, res);
+        }
+        return res;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void setObject(final List<T> object) {
+        final String expression = propertyExpression();
+        final Object target = getInnermostModelOrObject();
+        ((Map<String, List<T>>) PropertyResolver.getValue(expression, target)).put(key, object);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java
new file mode 100644
index 0000000..dd46075
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClause.java
@@ -0,0 +1,129 @@
+/*
+ * 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.panels.search;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+public final class SearchClause implements Serializable {
+
+    private static final long serialVersionUID = 2010794463096110104L;
+
+    public enum Operator {
+
+        AND,
+        OR;
+
+    }
+
+    public enum Type {
+
+        ATTRIBUTE,
+        MEMBERSHIP,
+        RESOURCE,
+        ENTITLEMENT;
+
+    }
+
+    public enum Comparator {
+
+        IS_NULL,
+        IS_NOT_NULL,
+        EQUALS,
+        NOT_EQUALS,
+        GREATER_OR_EQUALS,
+        GREATER_THAN,
+        LESS_OR_EQUALS,
+        LESS_THAN;
+
+    }
+
+    private Operator operator;
+
+    private Type type;
+
+    private String property;
+
+    private Comparator comparator;
+
+    private String value;
+
+    public SearchClause() {
+        setOperator(SearchClause.Operator.AND);
+        setComparator(SearchClause.Comparator.EQUALS);
+    }
+
+    public Operator getOperator() {
+        return operator;
+    }
+
+    public void setOperator(final Operator operator) {
+        this.operator = operator;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public void setType(final Type type) {
+        this.type = type;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+    public void setProperty(final String property) {
+        this.property = property;
+    }
+
+    public Comparator getComparator() {
+        return comparator;
+    }
+
+    public void setComparator(final Comparator comparator) {
+        this.comparator = comparator;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(final String value) {
+        this.value = value;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    @Override
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    @Override
+    public String toString() {
+        return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
new file mode 100644
index 0000000..561c744
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchClausePanel.java
@@ -0,0 +1,488 @@
+/*
+ * 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.panels.search;
+
+import static org.apache.syncope.client.console.panels.search.SearchClause.Type.ATTRIBUTE;
+
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.search.SearchClause.Comparator;
+import org.apache.syncope.client.console.panels.search.SearchClause.Operator;
+import org.apache.syncope.client.console.panels.search.SearchClause.Type;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel;
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.CheckBox;
+import org.apache.wicket.markup.html.form.FormComponent;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+
+public class SearchClausePanel extends FieldPanel<SearchClause> {
+
+    private static final long serialVersionUID = -527351923968737757L;
+
+    private final boolean required;
+
+    private final IModel<List<SearchClause.Type>> types;
+
+    private final IModel<List<String>> anames;
+
+    private final IModel<List<String>> dnames;
+
+    private final IModel<List<String>> groupNames;
+
+    private final IModel<List<String>> resourceNames;
+
+    private IModel<SearchClause> clause;
+
+    private final LoadableDetachableModel<List<Comparator>> comparators;
+
+    private final LoadableDetachableModel<List<String>> properties;
+
+    public SearchClausePanel(
+            final String id,
+            final String name,
+            final Model<SearchClause> clause,
+            final boolean required,
+            final IModel<List<SearchClause.Type>> types,
+            final IModel<List<String>> anames,
+            final IModel<List<String>> dnames,
+            final IModel<List<String>> groupNames,
+            final IModel<List<String>> resourceNames
+    ) {
+
+        super(id, name, clause);
+
+        this.clause = clause == null ? new Model<SearchClause>(null) : clause;
+
+        this.required = required;
+        this.types = types;
+        this.anames = anames;
+        this.dnames = dnames;
+        this.groupNames = groupNames;
+        this.resourceNames = resourceNames;
+
+        field = new FormComponent<SearchClause>("container", this.clause) {
+
+            private static final long serialVersionUID = 1L;
+
+        };
+
+        add(field);
+
+        comparators = new LoadableDetachableModel<List<Comparator>>() {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected List<Comparator> load() {
+                if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
+                    return Collections.<Comparator>emptyList();
+                }
+
+                switch (field.getModel().getObject().getType()) {
+                    case ATTRIBUTE:
+                        return Arrays.asList(SearchClause.Comparator.values());
+
+                    case MEMBERSHIP:
+                    case RESOURCE:
+                        return Arrays.asList(SearchClause.Comparator.EQUALS, SearchClause.Comparator.NOT_EQUALS);
+                    default:
+                        return Collections.<Comparator>emptyList();
+                }
+            }
+        };
+
+        properties = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected List<String> load() {
+                if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
+                    return Collections.<String>emptyList();
+                }
+
+                switch (field.getModel().getObject().getType()) {
+                    case ATTRIBUTE:
+                        final List<String> names = new ArrayList<String>(dnames.getObject());
+                        if (anames.getObject() != null && !anames.getObject().isEmpty()) {
+                            names.addAll(anames.getObject());
+                        }
+                        Collections.sort(names);
+                        return names;
+
+                    case MEMBERSHIP:
+                        return groupNames.getObject();
+
+                    case RESOURCE:
+                        return resourceNames.getObject();
+                    default:
+                        return Collections.<String>emptyList();
+                }
+            }
+        };
+    }
+
+    @Override
+    public SearchClause getModelObject() {
+        return this.clause.getObject();
+    }
+
+    @Override
+    public FieldPanel<SearchClause> setModelObject(final SearchClause object) {
+        this.clause.setObject(object);
+        return super.setModelObject(object);
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public FieldPanel<SearchClause> setNewModel(final ListItem item) {
+        clause.setObject(SearchClause.class.cast(item.getModelObject()));
+        return this;
+    }
+
+    @Override
+    public FieldPanel<SearchClause> setNewModel(final IModel<SearchClause> model) {
+        clause = model;
+        return super.setNewModel(model);
+    }
+
+    @Override
+    public final MarkupContainer add(final Component... childs) {
+        return super.add(childs);
+    }
+
+    @Override
+    public FieldPanel<SearchClause> settingsDependingComponents() {
+        final SearchClause searchClause = this.clause.getObject();
+
+        final WebMarkupContainer operatorContainer = new WebMarkupContainer("operatorContainer") {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void onComponentTag(final ComponentTag tag) {
+                super.onComponentTag(tag);
+                if (getIndex() == 0) {
+                    tag.append("class", "glyphicon glyphicon-search", " ");
+                }
+            }
+
+        };
+
+        operatorContainer.setOutputMarkupId(true);
+
+        field.add(operatorContainer);
+
+        final BootstrapToggleConfig config = new BootstrapToggleConfig();
+        config
+                .withOnStyle(BootstrapToggleConfig.Style.info).withOffStyle(BootstrapToggleConfig.Style.warning)
+                .withSize(BootstrapToggleConfig.Size.mini)
+                .withOnLabel("AND")
+                .withOffLabel("OR");
+
+        operatorContainer.add(new BootstrapToggle("operator", new Model<Boolean>() {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public Boolean getObject() {
+                return searchClause.getOperator() == Operator.AND;
+            }
+
+            @Override
+            public void setObject(final Boolean object) {
+                searchClause.setOperator(object ? Operator.AND : Operator.OR);
+            }
+        }, config) {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected IModel<String> getOffLabel() {
+                return Model.of(getString("Off", null, "Off"));
+            }
+
+            @Override
+            protected IModel<String> getOnLabel() {
+                return Model.of(getString("On", null, "On"));
+            }
+
+            @Override
+            protected CheckBox newCheckBox(final String id, final IModel<Boolean> model) {
+                final CheckBox checkBox = super.newCheckBox(id, model);
+                checkBox.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = 1L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                    }
+                });
+                return checkBox;
+            }
+        }.setVisible(getIndex() > 0).setOutputMarkupPlaceholderTag(true));
+
+        final AjaxDropDownChoicePanel<String> property = new AjaxDropDownChoicePanel<>(
+                "property", "property", new PropertyModel<String>(searchClause, "property"));
+        property.hideLabel().setRequired(required).setOutputMarkupId(true);
+        property.setChoices(properties);
+        field.add(property);
+
+        final AjaxDropDownChoicePanel<SearchClause.Comparator> comparator = new AjaxDropDownChoicePanel<>(
+                "comparator", "comparator", new PropertyModel<SearchClause.Comparator>(searchClause, "comparator"));
+        comparator.setChoices(comparators);
+        comparator.setNullValid(false).hideLabel().setOutputMarkupId(true);
+        comparator.setRequired(required);
+        comparator.setChoiceRenderer(getComparatorRender(field.getModel()));
+        field.add(comparator);
+
+        final AjaxTextFieldPanel value = new AjaxTextFieldPanel(
+                "value", "value", new PropertyModel<String>(searchClause, "value"));
+        value.hideLabel().setOutputMarkupId(true);
+        field.add(value);
+
+        final AjaxDropDownChoicePanel<SearchClause.Type> type = new AjaxDropDownChoicePanel<>(
+                "type", "type", new PropertyModel<SearchClause.Type>(searchClause, "type"));
+        type.setChoices(types).hideLabel().setRequired(required).setOutputMarkupId(true);
+        type.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                setFieldAccess(searchClause.getType(), comparator, value);
+                target.add(property);
+                target.add(comparator);
+                target.add(value);
+            }
+        });
+        field.add(type);
+
+        comparator.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                if (type.getModelObject() == SearchClause.Type.ATTRIBUTE) {
+                    if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
+                            || comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
+
+                        value.setModelObject(null);
+                        value.setEnabled(false);
+                    } else {
+                        value.setEnabled(true);
+                    }
+                    target.add(value);
+                }
+            }
+        });
+
+        setFieldAccess(searchClause.getType(), comparator, value);
+
+        return this;
+    }
+
+    private void setFieldAccess(
+            final Type type, final FieldPanel<Comparator> comparator, final FieldPanel<String> value) {
+        if (type != null) {
+            switch (type) {
+                case ATTRIBUTE:
+                    if (!comparator.isEnabled()) {
+                        comparator.setEnabled(true);
+                        comparator.setRequired(true);
+                    }
+
+                    value.setEnabled(comparator.getModelObject() != SearchClause.Comparator.IS_NULL
+                            && comparator.getModelObject() != SearchClause.Comparator.IS_NOT_NULL);
+                    break;
+                default:
+                    value.setEnabled(false);
+                    value.setModelObject("");
+            }
+        }
+    }
+
+    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();
+                }
+
+                String display;
+
+                switch (clause.getObject().getType()) {
+                    case ATTRIBUTE:
+                        switch (object) {
+                            case IS_NULL:
+                                display = "NULL";
+                                break;
+
+                            case IS_NOT_NULL:
+                                display = "NOT NULL";
+                                break;
+
+                            case EQUALS:
+                                display = "==";
+                                break;
+
+                            case NOT_EQUALS:
+                                display = "!=";
+                                break;
+
+                            case LESS_THAN:
+                                display = "<";
+                                break;
+
+                            case LESS_OR_EQUALS:
+                                display = "<=";
+                                break;
+
+                            case GREATER_THAN:
+                                display = ">";
+                                break;
+
+                            case GREATER_OR_EQUALS:
+                                display = ">=";
+                                break;
+
+                            default:
+                                display = StringUtils.EMPTY;
+                        }
+                        break;
+                    case MEMBERSHIP:
+                        switch (object) {
+                            case EQUALS:
+                                display = "IN";
+                                break;
+
+                            case NOT_EQUALS:
+                                display = "NOT IN";
+                                break;
+
+                            default:
+                                display = StringUtils.EMPTY;
+                        }
+                        break;
+                    case RESOURCE:
+                        switch (object) {
+                            case EQUALS:
+                                display = "HAS";
+                                break;
+
+                            case NOT_EQUALS:
+                                display = "HAS NOT";
+                                break;
+
+                            default:
+                                display = StringUtils.EMPTY;
+                        }
+                        break;
+                    default:
+                        display = object.toString();
+                }
+                return display;
+            }
+
+            @Override
+            public String getIdValue(final SearchClause.Comparator object, final int index) {
+                return getDisplayValue(object).toString();
+            }
+
+            @Override
+            public SearchClause.Comparator getObject(
+                    final String id, final IModel<? extends List<? extends SearchClause.Comparator>> choices) {
+
+                final SearchClause.Comparator res;
+                switch (id) {
+                    case "HAS":
+                    case "IN":
+                        res = SearchClause.Comparator.EQUALS;
+                        break;
+                    case "HAS NOT":
+                    case "NOT IN":
+                        res = SearchClause.Comparator.NOT_EQUALS;
+                        break;
+                    case "NULL":
+                        res = SearchClause.Comparator.IS_NULL;
+                        break;
+                    case "NOT NULL":
+                        res = SearchClause.Comparator.IS_NOT_NULL;
+                        break;
+                    case "==":
+                        res = SearchClause.Comparator.EQUALS;
+                        break;
+                    case "!=":
+                        res = SearchClause.Comparator.NOT_EQUALS;
+                        break;
+                    case "<":
+                        res = SearchClause.Comparator.LESS_THAN;
+                        break;
+                    case "<=":
+                        res = SearchClause.Comparator.LESS_OR_EQUALS;
+                        break;
+                    case ">":
+                        res = SearchClause.Comparator.GREATER_THAN;
+                        break;
+                    case ">=":
+                        res = SearchClause.Comparator.GREATER_OR_EQUALS;
+                        break;
+                    default:
+                        // EQUALS to be used as default value
+                        res = SearchClause.Comparator.EQUALS;
+                        break;
+                }
+                return res;
+            }
+        };
+    }
+
+    @Override
+    public FieldPanel<SearchClause> clone() {
+        final SearchClausePanel panel = new SearchClausePanel(
+                getId(), name, null, required, types, anames, dnames, groupNames, resourceNames);
+        panel.setReadOnly(this.isReadOnly());
+        panel.setRequired(this.isRequired());
+        return panel;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
new file mode 100644
index 0000000..bfbe314
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/SearchUtils.java
@@ -0,0 +1,266 @@
+/*
+ * 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.panels.search;
+
+import static org.apache.syncope.client.console.panels.search.AbstractSearchPanel.LOG;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.cxf.jaxrs.ext.search.ConditionType;
+import org.apache.cxf.jaxrs.ext.search.SearchBean;
+import org.apache.cxf.jaxrs.ext.search.SearchCondition;
+import org.apache.cxf.jaxrs.ext.search.client.CompleteCondition;
+import org.apache.cxf.jaxrs.ext.search.fiql.FiqlParser;
+import org.apache.syncope.common.lib.search.AbstractFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.search.AnyObjectFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+import org.apache.syncope.common.lib.search.SyncopeProperty;
+import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
+
+public final class SearchUtils implements Serializable {
+
+    private static final long serialVersionUID = 398381905376547084L;
+
+    private SearchUtils() {
+
+    }
+
+    public static Map<String, List<SearchClause>> getSearchClauses(final Map<String, String> fiql) {
+        final Map<String, List<SearchClause>> res = new HashMap<>();
+        if (fiql != null && !fiql.isEmpty()) {
+            for (Map.Entry<String, String> entry : fiql.entrySet()) {
+                res.put(entry.getKey(), getSearchClauses(entry.getValue()));
+            }
+        }
+        return res;
+    }
+
+    public static List<SearchClause> getSearchClauses(final String fiql) {
+        final List<SearchClause> res = new ArrayList<>();
+        if (StringUtils.isNotBlank(fiql)) {
+            try {
+                FiqlParser<SearchBean> fiqlParser = new FiqlParser<>(
+                        SearchBean.class, AbstractFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
+                res.addAll(getSearchClauses(fiqlParser.parse(fiql)));
+            } catch (Exception e) {
+                LOG.error("Unparseable FIQL expression '{}'", fiql, e);
+            }
+        }
+        return res;
+    }
+
+    public static List<SearchClause> getSearchClauses(final SearchCondition<SearchBean> sc) {
+        List<SearchClause> res = new ArrayList<SearchClause>();
+
+        if (sc.getStatement() == null) {
+            res.addAll(getCompoundSearchClause(sc));
+        } else {
+            res.add(getPrimitiveSearchClause(sc));
+        }
+
+        return res;
+    }
+
+    public static List<SearchClause> getCompoundSearchClause(final SearchCondition<SearchBean> sc) {
+        List<SearchClause> res = new ArrayList<SearchClause>();
+
+        for (SearchCondition<SearchBean> searchCondition : sc.getSearchConditions()) {
+            if (searchCondition.getStatement() == null) {
+                res.addAll(getCompoundSearchClause(searchCondition));
+            } else {
+                SearchClause clause = getPrimitiveSearchClause(searchCondition);
+                if (sc.getConditionType() == ConditionType.AND) {
+                    clause.setOperator(SearchClause.Operator.AND);
+                }
+                if (sc.getConditionType() == ConditionType.OR) {
+                    clause.setOperator(SearchClause.Operator.OR);
+                }
+                res.add(clause);
+            }
+        }
+
+        return res;
+    }
+
+    public static SearchClause getPrimitiveSearchClause(final SearchCondition<SearchBean> sc) {
+        SearchClause res = new SearchClause();
+
+        String property = sc.getCondition().getKeySet().iterator().next();
+        res.setProperty(property);
+        String value = sc.getCondition().get(property);
+        res.setValue(value);
+
+        LOG.info("Condition: " + sc.getCondition());
+
+        if (SpecialAttr.GROUPS.toString().equals(property)) {
+            res.setType(SearchClause.Type.MEMBERSHIP);
+            // check if the following is really required
+
+//            for (String label : groupNames.getObject()) {
+//                if (value.equals(label.substring(0, label.indexOf(' ')))) {
+//                    searchClause.setProperty(label);
+//                }
+//            }
+        } else if (SpecialAttr.RESOURCES.toString().equals(property)) {
+            res.setType(SearchClause.Type.RESOURCE);
+            res.setProperty(value);
+        } else {
+            res.setType(SearchClause.Type.ATTRIBUTE);
+        }
+
+        switch (sc.getConditionType()) {
+            case EQUALS:
+                res.setComparator(SpecialAttr.NULL.toString().equals(value)
+                        ? SearchClause.Comparator.IS_NULL : SearchClause.Comparator.EQUALS);
+                break;
+
+            case NOT_EQUALS:
+                res.setComparator(SpecialAttr.NULL.toString().equals(value)
+                        ? SearchClause.Comparator.IS_NOT_NULL : SearchClause.Comparator.NOT_EQUALS);
+                break;
+
+            case GREATER_OR_EQUALS:
+                res.setComparator(SearchClause.Comparator.GREATER_OR_EQUALS);
+                break;
+
+            case GREATER_THAN:
+                res.setComparator(SearchClause.Comparator.GREATER_THAN);
+                break;
+
+            case LESS_OR_EQUALS:
+                res.setComparator(SearchClause.Comparator.LESS_OR_EQUALS);
+                break;
+
+            case LESS_THAN:
+                res.setComparator(SearchClause.Comparator.LESS_THAN);
+                break;
+
+            default:
+                break;
+        }
+
+        return res;
+    }
+
+    public static String buildFIQL(final List<SearchClause> clauses, final AbstractFiqlSearchConditionBuilder builder) {
+        LOG.debug("Generating FIQL from List<SearchClause>: {}", clauses);
+
+        CompleteCondition prevCondition;
+        CompleteCondition condition = null;
+
+        boolean notTheFirst = false;
+
+        for (SearchClause clause : clauses) {
+            if (clause.getType() != null && StringUtils.isNotBlank(clause.getProperty())) {
+                prevCondition = condition;
+
+                switch (clause.getType()) {
+                    case MEMBERSHIP:
+                        Long groupId = NumberUtils.toLong(clause.getProperty().split(" ")[0]);
+
+                        if (builder instanceof UserFiqlSearchConditionBuilder) {
+                            condition = clause.getComparator() == SearchClause.Comparator.EQUALS
+                                    ? ((UserFiqlSearchConditionBuilder) builder).inGroups(groupId)
+                                    : ((UserFiqlSearchConditionBuilder) builder).notInGroups(groupId);
+                        } else {
+                            condition = clause.getComparator() == SearchClause.Comparator.EQUALS
+                                    ? ((AnyObjectFiqlSearchConditionBuilder) builder).inGroups(groupId)
+                                    : ((AnyObjectFiqlSearchConditionBuilder) builder).notInGroups(groupId);
+                        }
+                        break;
+
+                    case RESOURCE:
+                        condition = clause.getComparator() == SearchClause.Comparator.EQUALS
+                                ? builder.hasResources(clause.getProperty())
+                                : builder.hasNotResources(clause.getProperty());
+                        break;
+
+                    case ATTRIBUTE:
+                        SyncopeProperty property = builder.is(clause.getProperty());
+                        switch (clause.getComparator()) {
+                            case IS_NULL:
+                                condition = builder.isNull(clause.getProperty());
+                                break;
+
+                            case IS_NOT_NULL:
+                                condition = builder.isNotNull(clause.getProperty());
+                                break;
+
+                            case LESS_THAN:
+                                condition = StringUtils.isNumeric(clause.getProperty())
+                                        ? property.lessThan(NumberUtils.toDouble(clause.getValue()))
+                                        : property.lexicalBefore(clause.getValue());
+                                break;
+
+                            case LESS_OR_EQUALS:
+                                condition = StringUtils.isNumeric(clause.getProperty())
+                                        ? property.lessOrEqualTo(NumberUtils.toDouble(clause.getValue()))
+                                        : property.lexicalNotAfter(clause.getValue());
+                                break;
+
+                            case GREATER_THAN:
+                                condition = StringUtils.isNumeric(clause.getProperty())
+                                        ? property.greaterThan(NumberUtils.toDouble(clause.getValue()))
+                                        : property.lexicalAfter(clause.getValue());
+                                break;
+
+                            case GREATER_OR_EQUALS:
+                                condition = StringUtils.isNumeric(clause.getProperty())
+                                        ? property.greaterOrEqualTo(NumberUtils.toDouble(clause.getValue()))
+                                        : property.lexicalNotBefore(clause.getValue());
+                                break;
+
+                            case NOT_EQUALS:
+                                condition = property.notEqualTo(clause.getValue());
+                                break;
+
+                            case EQUALS:
+                            default:
+                                condition = property.equalTo(clause.getValue());
+                                break;
+                        }
+                        break;
+                    default:
+                        break;
+                }
+
+                if (notTheFirst) {
+                    if (clause.getOperator() == SearchClause.Operator.AND) {
+                        condition = builder.and(prevCondition, condition);
+                    }
+                    if (clause.getOperator() == SearchClause.Operator.OR) {
+                        condition = builder.or(prevCondition, condition);
+                    }
+                }
+
+                notTheFirst = true;
+            }
+        }
+
+        String fiql = condition == null ? null : condition.query();
+        LOG.debug("Generated FIQL: {}", fiql);
+
+        return fiql;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java
new file mode 100644
index 0000000..bf6a4fc
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/search/UserSearchPanel.java
@@ -0,0 +1,46 @@
+/*
+ * 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.panels.search;
+
+import java.util.List;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.model.PropertyModel;
+
+public final class UserSearchPanel extends AnyObjectSearchPanel {
+
+    private static final long serialVersionUID = -1769527800450203738L;
+
+    public static class Builder extends AnyObjectSearchPanel.Builder {
+
+        private static final long serialVersionUID = 6308997285778809578L;
+
+        public Builder(final PropertyModel<List<SearchClause>> model) {
+            super(model);
+        }
+
+        @Override
+        public UserSearchPanel build(final String id) {
+            return new UserSearchPanel(id, this);
+        }
+    }
+
+    private UserSearchPanel(final String id, final Builder builder) {
+        super(id, AnyTypeKind.USER, builder);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java
index 5a44086..1d5171c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/FieldPanel.java
@@ -37,6 +37,8 @@ public abstract class FieldPanel<T extends Serializable> extends AbstractFieldPa
 
     protected String title = null;
 
+    private final Model<Integer> index = Model.of(0);
+
     public FieldPanel(final String id, final IModel<T> model) {
         this(id, id, model);
     }
@@ -164,6 +166,25 @@ public abstract class FieldPanel<T extends Serializable> extends AbstractFieldPa
         });
     }
 
+    public FieldPanel<T> setIndex(final int index) {
+        this.index.setObject(index);
+        return this;
+    }
+
+    public int getIndex() {
+        return index.getObject();
+    }
+
+    /**
+     * To be overridded to add settings depending components.
+     * It has to be used by default to add components depending by index model.
+     *
+     * @return the current field panel.
+     */
+    public FieldPanel<T> settingsDependingComponents() {
+        return this;
+    }
+
     @Override
     @SuppressWarnings("unchecked")
     public FieldPanel<T> clone() {

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/MultiFieldPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/MultiFieldPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/MultiFieldPanel.java
index 7b7ffcb..906ea02 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/MultiFieldPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/MultiFieldPanel.java
@@ -34,7 +34,7 @@ import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.ResourceModel;
 
-public final class MultiFieldPanel<E extends Serializable> extends AbstractFieldPanel<List<E>> {
+public abstract class MultiFieldPanel<E extends Serializable> extends AbstractFieldPanel<List<E>> {
 
     private static final long serialVersionUID = -6322397761456513324L;
 
@@ -71,7 +71,8 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
         container.add(form);
         // -----------------------
 
-        if (model.getObject() != null && model.getObject().isEmpty()) {
+        final List<E> obj = model.getObject();
+        if (obj == null || obj.isEmpty()) {
             form.addOrReplace(getNoDataFragment(model, name));
         } else {
             form.addOrReplace(getDataFragment(model, name));
@@ -94,8 +95,10 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
 
             @Override
             protected void populateItem(final ListItem<E> item) {
-
                 final FieldPanel<? extends Serializable> fieldPanel = panelTemplate.clone();
+                fieldPanel.setIndex(item.getIndex());
+                fieldPanel.setNewModel(item);
+                fieldPanel.settingsDependingComponents();
 
                 if (eventTemplate) {
                     fieldPanel.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
@@ -109,7 +112,6 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
                     });
                 }
 
-                fieldPanel.setNewModel(item);
                 item.add(fieldPanel.hideLabel().setRenderBodyOnly(true));
 
                 final AjaxSubmitLink minus = new AjaxSubmitLink("drop") {
@@ -132,6 +134,12 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
                             send(getPage(), Broadcast.BREADTH, new MultiValueSelectorEvent(target));
                         }
                     }
+
+                    @Override
+                    protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+                        error(getString(Constants.OPERATION_ERROR));
+                        super.onError(target, form);
+                    }
                 };
 
                 item.add(minus);
@@ -143,7 +151,7 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
                     fragment = new Fragment("panelPlus", "emptyFragment", MultiFieldPanel.this);
                 }
 
-                item.add(fragment);
+                item.add(fragment.setRenderBodyOnly(true));
             }
         };
 
@@ -160,7 +168,7 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
             @Override
             protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
                 //Add current component
-                model.getObject().add(null);
+                model.getObject().add(newModelObject());
 
                 if (model.getObject().size() == 1) {
                     form.addOrReplace(getDataFragment(model, label));
@@ -168,6 +176,12 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
 
                 target.add(container);
             }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+                error(getString(Constants.OPERATION_ERROR));
+                super.onError(target, form);
+            }
         };
 
         final Fragment fragment = new Fragment("panelPlus", "fragmentPlus", MultiFieldPanel.this);
@@ -200,7 +214,11 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
         }
     }
 
-    public static class Builder<E extends Serializable> {
+    protected abstract E newModelObject();
+
+    public static class Builder<E extends Serializable> implements Serializable {
+
+        private static final long serialVersionUID = 1L;
 
         private final IModel<List<E>> model;
 
@@ -261,8 +279,26 @@ public final class MultiFieldPanel<E extends Serializable> extends AbstractField
             return this;
         }
 
+        /**
+         * Default model object instance.
+         *
+         * @return default model object instance.
+         */
+        protected E newModelObject() {
+            return null;
+        }
+
         public MultiFieldPanel<E> build(final String id, final String name, final FieldPanel<E> panelTemplate) {
-            return new MultiFieldPanel<>(id, name, model, panelTemplate, eventTemplate);
+            return new MultiFieldPanel<E>(id, name, model, panelTemplate, eventTemplate) {
+
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                protected E newModelObject() {
+                    return Builder.this.newModelObject();
+                }
+
+            };
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java
index ec4ccd9..173e4d5 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardBuilder.java
@@ -20,7 +20,9 @@ import org.apache.commons.lang3.SerializationUtils;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.extensions.wizard.WizardModel;
 
-public abstract class AjaxWizardBuilder<T extends Serializable> {
+public abstract class AjaxWizardBuilder<T extends Serializable> implements Serializable {
+
+    private static final long serialVersionUID = 5241745929825564456L;
 
     private final String id;
 
@@ -81,6 +83,10 @@ public abstract class AjaxWizardBuilder<T extends Serializable> {
         return item;
     }
 
+    public T getDefaultItem() {
+        return defaultItem;
+    }
+
     private T newModelObject() {
         if (item == null) {
             // keep the original item: the which one before the changes performed during wizard browsing

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButton.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButton.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButton.java
index 0732f3a..b9f1325 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButton.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButton.java
@@ -24,7 +24,7 @@ import org.apache.wicket.model.ResourceModel;
 
 public abstract class AjaxWizardButton extends AjaxButton {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = -9147736051493629209L;
 
     private final IWizard wizard;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButtonBar.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButtonBar.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButtonBar.java
index 0719c50..2711e28 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButtonBar.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/AjaxWizardButtonBar.java
@@ -24,14 +24,14 @@ import org.apache.wicket.markup.html.form.Form;
 
 public class AjaxWizardButtonBar extends WizardButtonBar {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 5641095671558703391L;
 
     public AjaxWizardButtonBar(final String id, final AjaxWizard<?> wizard, final boolean edit) {
         super(id, wizard);
 
         addOrReplace(new AjaxWizardButton("next", wizard, "next") {
 
-            private static final long serialVersionUID = 1L;
+            private static final long serialVersionUID = 1773811852118436784L;
 
             @Override
             protected void onClick(final AjaxRequestTarget target, final Form<?> form) {
@@ -66,7 +66,7 @@ public class AjaxWizardButtonBar extends WizardButtonBar {
 
         addOrReplace(new AjaxWizardButton("previous", wizard, "prev", false) {
 
-            private static final long serialVersionUID = 1L;
+            private static final long serialVersionUID = 5704878742768853867L;
 
             @Override
             protected void onClick(final AjaxRequestTarget target, final Form<?> form) {
@@ -83,7 +83,7 @@ public class AjaxWizardButtonBar extends WizardButtonBar {
 
         addOrReplace(new AjaxWizardButton("cancel", wizard, "cancel", false) {
 
-            private static final long serialVersionUID = 1L;
+            private static final long serialVersionUID = 5704878742768853867L;
 
             @Override
             protected void onClick(final AjaxRequestTarget target, final Form<?> form) {
@@ -99,7 +99,7 @@ public class AjaxWizardButtonBar extends WizardButtonBar {
 
         addOrReplace(new AjaxWizardButton("finish", wizard, "finish") {
 
-            private static final long serialVersionUID = 1L;
+            private static final long serialVersionUID = 1773811852118436784L;
 
             @Override
             protected void onClick(final AjaxRequestTarget target, final Form<?> form) {

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
index 2e967f4..974d3dd 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/Details.java
@@ -27,7 +27,7 @@ import org.apache.wicket.model.IModel;
 
 public class Details<T extends AnyTO> extends WizardStep {
 
-    private static final long serialVersionUID = 6592027822510220463L;
+    private static final long serialVersionUID = -8995647450549098844L;
 
     protected final PageReference pageRef;
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/c50dae60/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java
index 582e56b..9705fc4 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/GroupDetails.java
@@ -18,12 +18,24 @@
  */
 package org.apache.syncope.client.console.wizards.any;
 
+import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.Collapsible;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.commons.JexlHelpUtils;
 import org.apache.syncope.client.console.commons.status.StatusBean;
+import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel;
+import org.apache.syncope.client.console.panels.search.MapOfListModel;
+import org.apache.syncope.client.console.panels.search.SearchClause;
+import org.apache.syncope.client.console.panels.search.UserSearchPanel;
+import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.rest.GroupRestClient;
 import org.apache.syncope.client.console.rest.UserRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
+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.AnyTypeKind;
@@ -34,9 +46,18 @@ import org.apache.wicket.ajax.markup.html.AjaxLink;
 import org.apache.wicket.event.IEvent;
 import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink;
 import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
+import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.Model;
 import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -50,11 +71,13 @@ public class GroupDetails extends Details<GroupTO> {
 
     private final GroupRestClient groupRestClient = new GroupRestClient();
 
+    private final AnyTypeRestClient anyTypeRestClient = new AnyTypeRestClient();
+
     private final WebMarkupContainer ownerContainer;
 
-    private final OwnerModel userOwnerModel;
+    private final OwnerModel uOwnerModel;
 
-    private final OwnerModel groupOwnerModel;
+    private final OwnerModel gOwnerModel;
 
     public GroupDetails(
             final GroupTO groupTO,
@@ -64,6 +87,22 @@ public class GroupDetails extends Details<GroupTO> {
             final boolean includeStatusPanel) {
         super(groupTO, statusModel, pageRef, includeStatusPanel);
 
+        final LoadableDetachableModel<List<AnyTypeTO>> types = new LoadableDetachableModel<List<AnyTypeTO>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<AnyTypeTO> load() {
+                return CollectionUtils.select(anyTypeRestClient.getAll(), new Predicate<AnyTypeTO>() {
+
+                    @Override
+                    public boolean evaluate(final AnyTypeTO t) {
+                        return AnyTypeKind.USER != t.getKind() && AnyTypeKind.GROUP != t.getKind();
+                    }
+                }, new ArrayList<AnyTypeTO>());
+            }
+        };
+
         ownerContainer = new WebMarkupContainer("ownerContainer");
         ownerContainer.setOutputMarkupId(true);
         this.add(ownerContainer);
@@ -77,8 +116,8 @@ public class GroupDetails extends Details<GroupTO> {
         groupOwnerSelectWin.setCookieName("create-groupOwnerSelect-modal");
         this.add(groupOwnerSelectWin);
 
-        final AjaxTextFieldPanel name =
-                new AjaxTextFieldPanel("name", "name", new PropertyModel<String>(groupTO, "name"), false);
+        final AjaxTextFieldPanel name = new AjaxTextFieldPanel("name", "name",
+                new PropertyModel<String>(groupTO, "name"), false);
 
         final WebMarkupContainer jexlHelp = JexlHelpUtils.getJexlHelpWebContainer("jexlHelp");
 
@@ -92,11 +131,12 @@ public class GroupDetails extends Details<GroupTO> {
         }
         this.add(name);
 
-        userOwnerModel = new OwnerModel(groupTO, AnyTypeKind.USER);
+        uOwnerModel = new OwnerModel(groupTO, AnyTypeKind.USER);
         @SuppressWarnings("unchecked")
-        final AjaxTextFieldPanel userOwner = new AjaxTextFieldPanel("userOwner", "userOwner", userOwnerModel, false);
-        userOwner.setReadOnly(true);
-        userOwner.setOutputMarkupId(true);
+        final AjaxTextFieldPanel userOwner = new AjaxTextFieldPanel("userOwner", "userOwner", uOwnerModel, false);
+        userOwner.setPlaceholder("userOwner");
+        userOwner.hideLabel();
+        userOwner.setReadOnly(true).setOutputMarkupId(true);
         ownerContainer.add(userOwner);
         final AjaxLink<Void> userOwnerSelect = new IndicatingAjaxLink<Void>("userOwnerSelect") {
 
@@ -124,18 +164,18 @@ public class GroupDetails extends Details<GroupTO> {
 
             @Override
             public void onClick(final AjaxRequestTarget target) {
-                userOwnerModel.setObject(null);
+                uOwnerModel.setObject(null);
                 target.add(userOwner);
             }
         };
         ownerContainer.add(userOwnerReset.setEnabled(false));
 
-        groupOwnerModel = new OwnerModel(groupTO, AnyTypeKind.GROUP);
+        gOwnerModel = new OwnerModel(groupTO, AnyTypeKind.GROUP);
         @SuppressWarnings("unchecked")
-        final AjaxTextFieldPanel groupOwner =
-                new AjaxTextFieldPanel("groupOwner", "groupOwner", groupOwnerModel, false);
-        groupOwner.setReadOnly(true);
-        groupOwner.setOutputMarkupId(true);
+        final AjaxTextFieldPanel groupOwner = new AjaxTextFieldPanel("groupOwner", "groupOwner", gOwnerModel, false);
+        groupOwner.setPlaceholder("groupOwner");
+        groupOwner.hideLabel();
+        groupOwner.setReadOnly(true).setOutputMarkupId(true);
         ownerContainer.add(groupOwner);
         final AjaxLink<Void> groupOwnerSelect = new IndicatingAjaxLink<Void>("groupOwnerSelect") {
 
@@ -164,11 +204,56 @@ public class GroupDetails extends Details<GroupTO> {
 
             @Override
             public void onClick(final AjaxRequestTarget target) {
-                groupOwnerModel.setObject(null);
+                gOwnerModel.setObject(null);
                 target.add(groupOwner);
             }
         };
         ownerContainer.add(groupOwnerReset.setEnabled(false));
+
+        // ------------------------
+        // uDynMembershipCond
+        // ------------------------
+        add(new Collapsible("uDynMembershipCond", Collections.<ITab>singletonList(
+                new AbstractTab(new ResourceModel("uDynMembershipCond", "Dynamic USER Membership Conditions")) {
+
+            private static final long serialVersionUID = 1037272333056449378L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new UserSearchPanel.Builder(new PropertyModel<List<SearchClause>>(groupTO, "uDynClauses")).
+                        required(false).build(panelId);
+            }
+        }), Model.of(StringUtils.isBlank(groupTO.getUDynMembershipCond()) ? -1 : 0)).setOutputMarkupId(true));
+        // ------------------------ 
+
+        // ------------------------
+        // aDynMembershipConds
+        // ------------------------
+        add(new ListView<AnyTypeTO>("aDynMembershipCond", types) {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void populateItem(final ListItem<AnyTypeTO> item) {
+                final String key = item.getModelObject().getKey();
+                item.add(new Collapsible("aDynMembershipCond", Collections.<ITab>singletonList(
+                        new AbstractTab(new StringResourceModel(
+                                "aDynMembershipCond", this, new Model<AnyTypeTO>(item.getModelObject()))) {
+
+                    private static final long serialVersionUID = 1037272333056449378L;
+
+                    @Override
+                    public Panel getPanel(final String panelId) {
+                        return new AnyObjectSearchPanel.Builder(
+                                new MapOfListModel<SearchClause>(groupTO, "aDynClauses",
+                                        item.getModelObject().getKey())).
+                                required(false).build(panelId);
+                    }
+                }), Model.of(StringUtils.isBlank(groupTO.getADynMembershipConds().get(key)) ? -1 : 0))
+                        .setOutputMarkupId(true));
+            }
+        });
+        // ------------------------
     }
 
     /**
@@ -182,10 +267,10 @@ public class GroupDetails extends Details<GroupTO> {
         super.onEvent(event);
 
         if (event.getPayload() instanceof UserOwnerSelectPayload) {
-            userOwnerModel.setObject(((UserOwnerSelectPayload) event.getPayload()).getUserId());
+            uOwnerModel.setObject(((UserOwnerSelectPayload) event.getPayload()).getUserId());
         }
         if (event.getPayload() instanceof GroupOwnerSelectPayload) {
-            groupOwnerModel.setObject(((GroupOwnerSelectPayload) event.getPayload()).getGroupId());
+            gOwnerModel.setObject(((GroupOwnerSelectPayload) event.getPayload()).getGroupId());
         }
 
         if (event.getPayload() instanceof AjaxRequestTarget) {