http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/Todo.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/Todo.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/Todo.java
new file mode 100644
index 0000000..1329fe4
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/Todo.java
@@ -0,0 +1,245 @@
+/*
+ * 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.pages;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.syncope.client.console.SyncopeSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.PreferenceManager;
+import 
org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.rest.ApprovalRestClient;
+import 
org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.WorkflowFormTO;
+import org.apache.wicket.Page;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import 
org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import 
org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import 
org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import 
org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+public class Todo extends BasePage {
+
+    private static final long serialVersionUID = -7122136682275797903L;
+
+    @SpringBean
+    private ApprovalRestClient restClient;
+
+    @SpringBean
+    private PreferenceManager prefMan;
+
+    private final ModalWindow window;
+
+    private static final int WIN_HEIGHT = 400;
+
+    private static final int WIN_WIDTH = 600;
+
+    private WebMarkupContainer container;
+
+    private int paginatorRows;
+
+    public Todo(final PageParameters parameters) {
+        super(parameters);
+
+        add(window = new ModalWindow("editApprovalWin"));
+
+        container = new WebMarkupContainer("approvalContainer");
+
+        MetaDataRoleAuthorizationStrategy.authorize(
+                container, RENDER, xmlRolesReader.getEntitlement("Approval", 
"list"));
+
+        paginatorRows = prefMan.getPaginatorRows(getRequest(), 
Constants.PREF_TODO_PAGINATOR_ROWS);
+
+        List<IColumn<WorkflowFormTO, String>> columns = new 
ArrayList<IColumn<WorkflowFormTO, String>>();
+        columns.add(new PropertyColumn<WorkflowFormTO, String>(
+                new ResourceModel("taskId"), "taskId", "taskId"));
+        columns.add(new PropertyColumn<WorkflowFormTO, String>(
+                new ResourceModel("key"), "key", "key"));
+        columns.add(new PropertyColumn<WorkflowFormTO, String>(
+                new ResourceModel("description"), "description", 
"description"));
+        columns.add(new DatePropertyColumn<WorkflowFormTO>(
+                new ResourceModel("createTime"), "createTime", "createTime"));
+        columns.add(new DatePropertyColumn<WorkflowFormTO>(
+                new ResourceModel("dueDate"), "dueDate", "dueDate"));
+        columns.add(new PropertyColumn<WorkflowFormTO, String>(new 
ResourceModel("owner"), "owner", "owner"));
+        columns.add(new AbstractColumn<WorkflowFormTO, String>(new 
ResourceModel("actions", "")) {
+
+            private static final long serialVersionUID = 2054811145491901166L;
+
+            @Override
+            public String getCssClass() {
+                return "action";
+            }
+
+            @Override
+            public void populateItem(final 
Item<ICellPopulator<WorkflowFormTO>> cellItem, final String componentId,
+                    final IModel<WorkflowFormTO> model) {
+
+                final WorkflowFormTO formTO = model.getObject();
+
+                final ActionLinksPanel panel = new 
ActionLinksPanel(componentId, model, getPageReference());
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = 
-3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        try {
+                            restClient.claimForm(formTO.getTaskId());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (SyncopeClientException scee) {
+                            error(getString(Constants.ERROR) + ": " + 
scee.getMessage());
+                        }
+                        feedbackPanel.refresh(target);
+                        target.add(container);
+                    }
+                }, ActionLink.ActionType.CLAIM, "Approval");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = 
-3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        window.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = 
-7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new 
ApprovalModalPage(Todo.this.getPageReference(), window, formTO);
+                            }
+                        });
+
+                        window.show(target);
+                    }
+                }, ActionLink.ActionType.EDIT, "Approval",
+                        
SyncopeSession.get().getUsername().equals(formTO.getOwner()));
+
+                cellItem.add(panel);
+            }
+        });
+
+        final AjaxFallbackDefaultDataTable<WorkflowFormTO, String> 
approvalTable =
+                new AjaxFallbackDefaultDataTable<WorkflowFormTO, String>(
+                        "approvalTable", columns, new ApprovalProvider(), 
paginatorRows);
+        container.add(approvalTable);
+
+        container.setOutputMarkupId(true);
+        add(container);
+
+        @SuppressWarnings("rawtypes")
+        Form approvalPaginatorForm = new Form("paginatorForm");
+
+        MetaDataRoleAuthorizationStrategy.authorize(approvalPaginatorForm, 
RENDER,
+                xmlRolesReader.getEntitlement("Approval", "list"));
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final DropDownChoice rowsChooser = new DropDownChoice("rowsChooser",
+                new PropertyModel(this, "paginatorRows"), 
prefMan.getPaginatorChoices());
+
+        rowsChooser.add(new 
AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                prefMan.set(getRequest(), getResponse(), 
Constants.PREF_TODO_PAGINATOR_ROWS,
+                        String.valueOf(paginatorRows));
+                approvalTable.setItemsPerPage(paginatorRows);
+
+                target.add(container);
+            }
+        });
+
+        approvalPaginatorForm.add(rowsChooser);
+        add(approvalPaginatorForm);
+
+        window.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        window.setInitialHeight(WIN_HEIGHT);
+        window.setInitialWidth(WIN_WIDTH);
+        window.setCookieName("edit-approval-modal");
+
+        setWindowClosedCallback(window, container);
+    }
+
+    private class ApprovalProvider extends 
SortableDataProvider<WorkflowFormTO, String> {
+
+        private static final long serialVersionUID = -2311716167583335852L;
+
+        private final SortableDataProviderComparator<WorkflowFormTO> 
comparator;
+
+        public ApprovalProvider() {
+            super();
+            //Default sorting
+            setSort("key", SortOrder.ASCENDING);
+            comparator = new 
SortableDataProviderComparator<WorkflowFormTO>(this);
+        }
+
+        @Override
+        public Iterator<WorkflowFormTO> iterator(final long first, final long 
count) {
+            final List<WorkflowFormTO> list = restClient.getForms();
+
+            Collections.sort(list, comparator);
+
+            return list.subList((int) first, (int) first + (int) 
count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return restClient.getForms().size();
+        }
+
+        @Override
+        public IModel<WorkflowFormTO> model(final WorkflowFormTO 
configuration) {
+            return new AbstractReadOnlyModel<WorkflowFormTO>() {
+
+                private static final long serialVersionUID = 
-2566070996511906708L;
+
+                @Override
+                public WorkflowFormTO getObject() {
+                    return configuration;
+                }
+            };
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/UserModalPage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/UserModalPage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserModalPage.java
new file mode 100644
index 0000000..0215538
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserModalPage.java
@@ -0,0 +1,229 @@
+/*
+ * 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.pages;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.Mode;
+import org.apache.syncope.client.console.panels.DerAttrsPanel;
+import org.apache.syncope.client.console.panels.MembershipsPanel;
+import org.apache.syncope.client.console.panels.PlainAttrsPanel;
+import org.apache.syncope.client.console.panels.ResourcesPanel;
+import org.apache.syncope.client.console.panels.SecurityQuestionPanel;
+import org.apache.syncope.client.console.panels.UserDetailsPanel;
+import org.apache.syncope.client.console.panels.VirAttrsPanel;
+import 
org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
+
+/**
+ * Modal window with User form.
+ */
+public abstract class UserModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = 5002005009737457667L;
+
+    protected final PageReference pageRef;
+
+    protected final ModalWindow window;
+
+    protected UserTO userTO;
+
+    protected final Mode mode;
+
+    private Fragment fragment = null;
+
+    private final boolean resetPassword;
+
+    protected final AjaxCheckBoxPanel storePassword;
+
+    public UserModalPage(final PageReference callerPageRef, final ModalWindow 
window, final UserTO userTO,
+            final Mode mode, final boolean resetPassword) {
+
+        super();
+
+        this.pageRef = callerPageRef;
+        this.window = window;
+        this.userTO = userTO;
+        this.mode = mode;
+        this.resetPassword = resetPassword;
+
+        fragment = new Fragment("userModalFrag", "userModalEditFrag", this);
+        fragment.setOutputMarkupId(true);
+        add(fragment);
+
+        storePassword = new AjaxCheckBoxPanel("storePassword", "storePassword",
+                new Model<Boolean>(Boolean.TRUE));
+    }
+
+    public UserTO getUserTO() {
+        return userTO;
+    }
+
+    public void setUserTO(final UserTO userTO) {
+        this.userTO = userTO;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    protected Form setupEditPanel() {
+        fragment.add(new Label("id", userTO.getKey() == 0
+                ? StringUtils.EMPTY
+                : userTO.getUsername()));
+
+        fragment.add(new Label("new", userTO.getKey() == 0
+                ? new ResourceModel("new")
+                : new Model(StringUtils.EMPTY)));
+
+        final Form form = new Form("UserForm");
+        form.setModel(new CompoundPropertyModel(userTO));
+
+        //--------------------------------
+        // User details
+        //--------------------------------
+        form.add(new UserDetailsPanel("details", userTO, form, resetPassword, 
mode == Mode.TEMPLATE));
+
+        form.add(new Label("statuspanel", ""));
+
+        form.add(new Label("pwdChangeInfo", ""));
+
+        form.add(new Label("securityQuestion", ""));
+        form.addOrReplace(new SecurityQuestionPanel("securityQuestion", 
userTO));
+
+        form.add(new Label("accountinformation", ""));
+        //--------------------------------
+
+        //--------------------------------
+        // Store password internally checkbox
+        //--------------------------------
+        final Fragment storePwdFragment = new Fragment("storePwdFrag", 
"storePwdCheck", form);
+        storePwdFragment.setOutputMarkupId(true);
+        final Label storePasswordLabel = new Label("storePasswordLabel", new 
ResourceModel("storePassword"));
+        storePwdFragment.add(storePasswordLabel);
+        storePwdFragment.add(storePassword);
+        form.add(userTO.getKey() == 0 && mode != Mode.TEMPLATE
+                ? storePwdFragment : new Fragment("storePwdFrag", 
"emptyFragment", form));
+        //--------------------------------
+
+        //--------------------------------
+        // Attributes panel
+        //--------------------------------
+        form.add(new PlainAttrsPanel("plainAttrs", userTO, form, mode));
+        //--------------------------------
+
+        //--------------------------------
+        // Derived attributes panel
+        //--------------------------------
+        form.add(new DerAttrsPanel("derAttrs", userTO));
+        //--------------------------------
+
+        //--------------------------------
+        // Virtual attributes panel
+        //--------------------------------
+        form.add(new VirAttrsPanel("virAttrs", userTO, mode == Mode.TEMPLATE));
+        //--------------------------------
+
+        //--------------------------------
+        // Resources panel
+        //--------------------------------
+        form.add(new 
ResourcesPanel.Builder("resources").attributableTO(userTO).build());
+        //--------------------------------
+
+        //--------------------------------
+        // Roles panel
+        //--------------------------------
+        form.add(new MembershipsPanel("memberships", userTO, mode, null, 
getPageReference()));
+        //--------------------------------
+
+        final AjaxButton submit = getOnSubmit();
+
+        if (mode == Mode.ADMIN) {
+            String allowedRoles = userTO.getKey() == 0
+                    ? xmlRolesReader.getEntitlement("Users", "create")
+                    : xmlRolesReader.getEntitlement("Users", "update");
+            MetaDataRoleAuthorizationStrategy.authorize(submit, RENDER, 
allowedRoles);
+        }
+
+        fragment.add(form);
+        form.add(submit);
+        form.setDefaultButton(submit);
+
+        final AjaxButton cancel = new AjaxButton(CANCEL, new 
ResourceModel(CANCEL)) {
+
+            private static final long serialVersionUID = 530608535790823587L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final 
Form<?> form) {
+                window.close(target);
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final 
Form<?> form) {
+            }
+        };
+
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+
+        return form;
+    }
+
+    protected AjaxButton getOnSubmit() {
+        return new IndicatingAjaxButton(APPLY, new ResourceModel(SUBMIT)) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final 
Form<?> form) {
+                try {
+                    submitAction(target, form);
+
+                    if (pageRef.getPage() instanceof BasePage) {
+                        ((BasePage) pageRef.getPage()).setModalResult(true);
+                    }
+
+                    closeAction(target, form);
+                } catch (Exception e) {
+                    LOG.error("While creating or updating user", e);
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                    feedbackPanel.refresh(target);
+                }
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final 
Form<?> form) {
+                feedbackPanel.refresh(target);
+            }
+        };
+    }
+
+    protected abstract void submitAction(AjaxRequestTarget target, Form<?> 
form);
+
+    protected abstract void closeAction(AjaxRequestTarget target, Form<?> 
form);
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/UserOwnerSelectModalPage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/UserOwnerSelectModalPage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserOwnerSelectModalPage.java
new file mode 100644
index 0000000..ae8a5da
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserOwnerSelectModalPage.java
@@ -0,0 +1,81 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.AbstractSearchResultPanel;
+import 
org.apache.syncope.client.console.panels.SelectOnlyUserSearchResultPanel;
+import org.apache.syncope.client.console.panels.UserSearchPanel;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.ResourceModel;
+
+public class UserOwnerSelectModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = 2106489458494696439L;
+
+    public UserOwnerSelectModalPage(final PageReference pageRef, final 
ModalWindow window) {
+        super();
+
+        final SelectOnlyUserSearchResultPanel searchResult =
+                new SelectOnlyUserSearchResultPanel("searchResult", true, 
null, pageRef, window, userRestClient);
+        add(searchResult);
+
+        final Form<?> searchForm = new Form("searchForm");
+        add(searchForm);
+
+        final UserSearchPanel searchPanel = new 
UserSearchPanel.Builder("searchPanel").build();
+        searchForm.add(searchPanel);
+
+        searchForm.add(new IndicatingAjaxButton("search", new 
ResourceModel("search")) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final 
Form<?> form) {
+                final String searchCond = searchPanel.buildFIQL();
+                doSearch(target, searchCond, searchResult);
+
+                Session.get().getFeedbackMessages().clear();
+                searchPanel.getSearchFeedback().refresh(target);
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final 
Form<?> form) {
+                searchPanel.getSearchFeedback().refresh(target);
+            }
+        });
+
+    }
+
+    private void doSearch(final AjaxRequestTarget target, final String fiql,
+            final AbstractSearchResultPanel resultsetPanel) {
+
+        if (fiql == null) {
+            error(getString(Constants.SEARCH_ERROR));
+            return;
+        }
+
+        resultsetPanel.search(fiql, target);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/UserSelfModalPage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/UserSelfModalPage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserSelfModalPage.java
new file mode 100644
index 0000000..087bd69
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserSelfModalPage.java
@@ -0,0 +1,72 @@
+/*
+ * 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.pages;
+
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.syncope.client.console.commons.Mode;
+import org.apache.syncope.client.console.rest.UserSelfRestClient;
+import org.apache.syncope.common.lib.AttributableOperations;
+import org.apache.syncope.common.lib.mod.UserMod;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+/**
+ * Modal window with User form.
+ */
+public class UserSelfModalPage extends UserModalPage {
+
+    private static final long serialVersionUID = 603212869211672852L;
+
+    @SpringBean
+    private UserSelfRestClient restClient;
+
+    private final UserTO initialUserTO;
+
+    public UserSelfModalPage(final PageReference callerPageRef, final 
ModalWindow window, final UserTO userTO) {
+        super(callerPageRef, window, userTO, Mode.SELF, userTO.getKey() != 0);
+
+        this.initialUserTO = SerializationUtils.clone(userTO);
+        setupEditPanel();
+    }
+
+    @Override
+    protected void submitAction(final AjaxRequestTarget target, final Form<?> 
form) {
+        final UserTO updatedUserTO = (UserTO) form.getModelObject();
+
+        if (updatedUserTO.getKey() == 0) {
+            restClient.create(updatedUserTO, storePassword.getModelObject());
+        } else {
+            final UserMod userMod = AttributableOperations.diff(updatedUserTO, 
initialUserTO);
+
+            // update user only if it has changed
+            if (!userMod.isEmpty()) {
+                restClient.update(userMod);
+            }
+        }
+    }
+
+    @Override
+    protected void closeAction(final AjaxRequestTarget target, final Form<?> 
form) {
+        setResponsePage(new ResultStatusModalPage.Builder(window, 
userTO).mode(mode).build());
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/UserTemplateModalPage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/UserTemplateModalPage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserTemplateModalPage.java
new file mode 100644
index 0000000..2b04035
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/UserTemplateModalPage.java
@@ -0,0 +1,60 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.client.console.commons.Mode;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.form.Form;
+
+/**
+ * Modal window with User form.
+ */
+public class UserTemplateModalPage extends UserModalPage {
+
+    private static final long serialVersionUID = 511003221213581368L;
+
+    private final SyncTaskTO syncTaskTO;
+
+    public UserTemplateModalPage(final PageReference callerPageRef, final 
ModalWindow window,
+            final SyncTaskTO syncTaskTO) {
+
+        super(callerPageRef, window, syncTaskTO.getUserTemplate() == null
+                ? new UserTO()
+                : syncTaskTO.getUserTemplate(), Mode.TEMPLATE, true);
+
+        this.syncTaskTO = syncTaskTO;
+
+        setupEditPanel();
+    }
+
+    @Override
+    protected void submitAction(final AjaxRequestTarget target, final Form 
form) {
+        syncTaskTO.setUserTemplate((UserTO) form.getModelObject());
+        taskRestClient.updateSyncTask(syncTaskTO);
+    }
+
+    @Override
+    protected void closeAction(final AjaxRequestTarget target, final Form 
form) {
+        window.close(target);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/Users.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/Users.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/Users.java
new file mode 100644
index 0000000..98f93bf
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/Users.java
@@ -0,0 +1,166 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.AbstractSearchResultPanel;
+import 
org.apache.syncope.client.console.panels.AbstractSearchResultPanel.EventDataWrapper;
+import org.apache.syncope.client.console.panels.UserSearchPanel;
+import org.apache.syncope.client.console.panels.UserSearchResultPanel;
+import org.apache.syncope.client.console.rest.UserRestClient;
+import 
org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxButton;
+import 
org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxLink;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.Page;
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+public class Users extends BasePage {
+
+    private static final long serialVersionUID = 134681165644474568L;
+
+    private final static int EDIT_MODAL_WIN_HEIGHT = 550;
+
+    private final static int EDIT_MODAL_WIN_WIDTH = 800;
+
+    @SpringBean
+    private UserRestClient restClient;
+
+    public Users(final PageParameters parameters) {
+        super(parameters);
+
+        // Modal window for editing user attributes
+        final ModalWindow editModalWin = new ModalWindow("editModal");
+        editModalWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        editModalWin.setInitialHeight(EDIT_MODAL_WIN_HEIGHT);
+        editModalWin.setInitialWidth(EDIT_MODAL_WIN_WIDTH);
+        editModalWin.setCookieName("edit-modal");
+        add(editModalWin);
+
+        final AbstractSearchResultPanel searchResult =
+                new UserSearchResultPanel("searchResult", true, null, 
getPageReference(), restClient);
+        add(searchResult);
+
+        final AbstractSearchResultPanel listResult =
+                new UserSearchResultPanel("listResult", false, null, 
getPageReference(), restClient);
+        add(listResult);
+
+        // create new user
+        final AjaxLink<Void> createLink = new 
ClearIndicatingAjaxLink<Void>("createLink", getPageReference()) {
+
+            private static final long serialVersionUID = -7978723352517770644L;
+
+            @Override
+            protected void onClickInternal(final AjaxRequestTarget target) {
+                editModalWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                    private static final long serialVersionUID = 
-7834632442532690940L;
+
+                    @Override
+                    public Page createPage() {
+                        return new 
EditUserModalPage(Users.this.getPageReference(), editModalWin, new UserTO());
+                    }
+                });
+
+                editModalWin.show(target);
+            }
+        };
+        MetaDataRoleAuthorizationStrategy.authorize(
+                createLink, ENABLE, xmlRolesReader.getEntitlement("Users", 
"create"));
+        add(createLink);
+
+        setWindowClosedReloadCallback(editModalWin);
+
+        final Form<?> searchForm = new Form<>("searchForm");
+        add(searchForm);
+
+        final UserSearchPanel searchPanel = new 
UserSearchPanel.Builder("searchPanel").build();
+        searchForm.add(searchPanel);
+
+        final ClearIndicatingAjaxButton searchButton =
+                new ClearIndicatingAjaxButton("search", new 
ResourceModel("search"), getPageReference()) {
+
+                    private static final long serialVersionUID = 
-958724007591692537L;
+
+                    @Override
+                    protected void onSubmitInternal(final AjaxRequestTarget 
target, final Form<?> form) {
+                        final String fiql = searchPanel.buildFIQL();
+                        LOG.debug("FIQL: " + fiql);
+
+                        doSearch(target, fiql, searchResult);
+
+                        Session.get().getFeedbackMessages().clear();
+                        searchPanel.getSearchFeedback().refresh(target);
+                    }
+
+                    @Override
+                    protected void onError(final AjaxRequestTarget target, 
final Form<?> form) {
+
+                        searchPanel.getSearchFeedback().refresh(target);
+                    }
+                };
+
+        searchForm.add(searchButton);
+        searchForm.setDefaultButton(searchButton);
+    }
+
+    private void doSearch(final AjaxRequestTarget target, final String fiql,
+            final AbstractSearchResultPanel resultsetPanel) {
+
+        if (fiql == null) {
+            error(getString(Constants.SEARCH_ERROR));
+            return;
+        }
+
+        resultsetPanel.search(fiql, target);
+    }
+
+    private void setWindowClosedReloadCallback(final ModalWindow window) {
+        window.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
+
+            private static final long serialVersionUID = 8804221891699487139L;
+
+            @Override
+            public void onClose(final AjaxRequestTarget target) {
+                final EventDataWrapper data = new EventDataWrapper();
+                data.setTarget(target);
+                data.setCreate(true);
+
+                send(getPage(), Broadcast.BREADTH, data);
+
+                if (isModalResult()) {
+                    // reset modal result
+                    setModalResult(false);
+                    // set operation succeeded
+                    
getSession().info(getString(Constants.OPERATION_SUCCEEDED));
+                    // refresh feedback panel
+                    feedbackPanel.refresh(target);
+                }
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/ViewUserModalPage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/ViewUserModalPage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/ViewUserModalPage.java
new file mode 100644
index 0000000..d47e07f
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/ViewUserModalPage.java
@@ -0,0 +1,49 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.form.Form;
+
+public class ViewUserModalPage extends EditUserModalPage {
+
+    private static final long serialVersionUID = -8715255026876951611L;
+
+    public ViewUserModalPage(final PageReference callerPageRef, final 
ModalWindow window, final UserTO userTO) {
+        super(callerPageRef, window, userTO);
+        form.setEnabled(false);
+        form.get(CANCEL).setVisible(false);
+    }
+
+    @Override
+    protected AjaxButton getOnSubmit() {
+        AjaxButton submit = super.getOnSubmit();
+        submit.setVisible(false);
+        return submit;
+    }
+
+    @Override
+    protected void submitAction(final AjaxRequestTarget target, final Form 
form) {
+        // No submit allowed, read-only form
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/VirSchemaModalPage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/VirSchemaModalPage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/VirSchemaModalPage.java
new file mode 100644
index 0000000..425b9cb
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/VirSchemaModalPage.java
@@ -0,0 +1,126 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.client.console.commons.Constants;
+import 
org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
+import 
org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+
+/**
+ * Modal window with Schema form.
+ */
+public class VirSchemaModalPage extends AbstractSchemaModalPage<VirSchemaTO> {
+
+    private static final long serialVersionUID = 5979623248182851337L;
+
+    public VirSchemaModalPage(final AttributableType kind) {
+        super(kind);
+    }
+
+    @Override
+    public void setSchemaModalPage(final PageReference pageRef, final 
ModalWindow window,
+            VirSchemaTO schema, final boolean createFlag) {
+
+        if (schema == null) {
+            schema = new VirSchemaTO();
+        }
+
+        final Form<VirSchemaTO> schemaForm = new Form<>(FORM);
+
+        schemaForm.setModel(new CompoundPropertyModel<>(schema));
+
+        final AjaxTextFieldPanel name =
+                new AjaxTextFieldPanel("key", getString("key"), new 
PropertyModel<String>(schema, "key"));
+        name.addRequiredLabel();
+
+        name.setEnabled(createFlag);
+
+        final AjaxCheckBoxPanel readonly = new AjaxCheckBoxPanel("readonly", 
getString("readonly"),
+                new PropertyModel<Boolean>(schema, "readonly"));
+
+        final AjaxButton submit = new IndicatingAjaxButton(APPLY, new 
ResourceModel(SUBMIT)) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final 
Form<?> form) {
+                VirSchemaTO schemaTO = (VirSchemaTO) 
form.getDefaultModelObject();
+                try {
+                    if (createFlag) {
+                        schemaRestClient.createVirSchema(kind, schemaTO);
+                    } else {
+                        schemaRestClient.updateVirSchema(kind, schemaTO);
+                    }
+                    if (pageRef.getPage() instanceof BasePage) {
+                        ((BasePage) pageRef.getPage()).setModalResult(true);
+                    }
+
+                    window.close(target);
+                } catch (SyncopeClientException e) {
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                    feedbackPanel.refresh(target);
+                }
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final 
Form<?> form) {
+                feedbackPanel.refresh(target);
+            }
+        };
+
+        final AjaxButton cancel = new IndicatingAjaxButton(CANCEL, new 
ResourceModel(CANCEL)) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final 
Form<?> form) {
+                window.close(target);
+            }
+        };
+
+        cancel.setDefaultFormProcessing(false);
+
+        String allowedRoles = createFlag
+                ? xmlRolesReader.getEntitlement("Schema", "create")
+                : xmlRolesReader.getEntitlement("Schema", "update");
+
+        MetaDataRoleAuthorizationStrategy.authorize(submit, ENABLE, 
allowedRoles);
+
+        schemaForm.add(name);
+        schemaForm.add(readonly);
+
+        schemaForm.add(submit);
+        schemaForm.add(cancel);
+
+        add(schemaForm);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/WelcomePage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/WelcomePage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/WelcomePage.java
new file mode 100644
index 0000000..d9f884d
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/WelcomePage.java
@@ -0,0 +1,55 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.client.console.SyncopeApplication;
+import org.apache.syncope.client.console.commons.XMLRolesReader;
+import org.apache.syncope.client.console.rest.UserSelfRestClient;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+/**
+ * Welcome page to display after successful login.
+ */
+public class WelcomePage extends WebPage {
+
+    private static final long serialVersionUID = 8851399358753120581L;
+
+    @SpringBean
+    private XMLRolesReader xmlRolesReader;
+
+    @SpringBean
+    private UserSelfRestClient userSelfRestClient;
+
+    public WelcomePage(final PageParameters parameters) {
+        super(parameters);
+        setupNavigationPanel();
+        setupEditProfileModal();
+    }
+
+    //To prevent warning: "leaking this in constructor java" 
+    private void setupNavigationPanel() {
+        ((SyncopeApplication) getApplication()).setupNavigationPanel(this, 
xmlRolesReader, false);
+    }
+
+    private void setupEditProfileModal() {
+        ((SyncopeApplication) getApplication()).setupEditProfileModal(this, 
userSelfRestClient);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/XMLEditorPopupPage.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/pages/XMLEditorPopupPage.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/XMLEditorPopupPage.java
new file mode 100644
index 0000000..b5a8d10
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/pages/XMLEditorPopupPage.java
@@ -0,0 +1,90 @@
+/*
+ * 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.pages;
+
+import java.io.IOException;
+import javax.ws.rs.core.MediaType;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.rest.WorkflowRestClient;
+import 
org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxButton;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextArea;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+import org.apache.wicket.util.io.IOUtils;
+
+public class XMLEditorPopupPage extends BasePopupPage {
+
+    private static final long serialVersionUID = 5816041644635271734L;
+
+    @SpringBean
+    private WorkflowRestClient wfRestClient;
+
+    public XMLEditorPopupPage() {
+        Form wfForm = new Form("workflowDefForm");
+
+        String definition;
+        try {
+            definition = 
IOUtils.toString(wfRestClient.getDefinition(MediaType.APPLICATION_XML_TYPE));
+        } catch (IOException e) {
+            LOG.error("Could not get workflow definition", e);
+            definition = StringUtils.EMPTY;
+        }
+        final TextArea<String> workflowDefArea = new 
TextArea<String>("workflowDefArea", new Model<String>(definition));
+        wfForm.add(workflowDefArea);
+
+        AjaxButton submit =
+                new ClearIndicatingAjaxButton(APPLY, new 
Model<String>(getString(SUBMIT)), getPageReference()) {
+
+                    private static final long serialVersionUID = 
-958724007591692537L;
+
+                    @Override
+                    protected void onSubmitInternal(final AjaxRequestTarget 
target, final Form<?> form) {
+                        try {
+                            wfRestClient.updateDefinition(
+                                    MediaType.APPLICATION_XML_TYPE, 
workflowDefArea.getModelObject());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (SyncopeClientException scee) {
+                            error(getString(Constants.ERROR) + ": " + 
scee.getMessage());
+                        }
+                        feedbackPanel.refresh(target);
+                    }
+
+                    @Override
+                    protected void onError(final AjaxRequestTarget target, 
final Form<?> form) {
+                        feedbackPanel.refresh(target);
+                    }
+                };
+
+        final Button close = new Button("closePage", new 
Model<String>(getString(CANCEL)));
+
+        MetaDataRoleAuthorizationStrategy.authorize(
+                submit, ENABLE, xmlRolesReader.getEntitlement("Configuration", 
"workflowDefUpdate"));
+        wfForm.add(submit);
+        wfForm.add(close);
+        this.add(wfForm);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractExtensionPanel.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractExtensionPanel.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractExtensionPanel.java
new file mode 100644
index 0000000..a785e9f
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractExtensionPanel.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import org.apache.syncope.client.console.commons.XMLRolesReader;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+public abstract class AbstractExtensionPanel extends Panel {
+
+    private static final long serialVersionUID = 4627828052717627159L;
+
+    @SpringBean
+    protected XMLRolesReader xmlRolesReader;
+
+    protected PageReference pageref;
+
+    public AbstractExtensionPanel(final String id, final PageReference 
pageref) {
+        super(id);
+        this.pageref = pageref;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractProvisioningTasksPanel.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractProvisioningTasksPanel.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractProvisioningTasksPanel.java
new file mode 100644
index 0000000..e066aac
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractProvisioningTasksPanel.java
@@ -0,0 +1,160 @@
+/*
+ * 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;
+
+import java.util.List;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.pages.PushTaskModalPage;
+import org.apache.syncope.client.console.pages.SyncTaskModalPage;
+import org.apache.syncope.client.console.pages.Tasks;
+import org.apache.syncope.client.console.pages.Tasks.TasksProvider;
+import 
org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxLink;
+import org.apache.syncope.common.lib.to.AbstractProvisioningTaskTO;
+import org.apache.syncope.common.lib.to.AbstractTaskTO;
+import org.apache.syncope.common.lib.to.PushTaskTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.wicket.Page;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.request.http.WebResponse;
+
+public abstract class AbstractProvisioningTasksPanel<T extends 
AbstractProvisioningTaskTO> extends AbstractTasks {
+
+    private static final long serialVersionUID = -8674781241465369244L;
+
+    private final int paginatorRows;
+
+    protected WebMarkupContainer container;
+
+    protected ModalWindow window;
+
+    protected AjaxDataTablePanel<AbstractTaskTO, String> table;
+
+    private final Class<T> reference;
+
+    public AbstractProvisioningTasksPanel(final String id, final PageReference 
pageRef, final Class<T> reference) {
+        super(id, pageRef);
+
+        this.reference = reference;
+
+        container = new WebMarkupContainer("container");
+        container.setOutputMarkupId(true);
+        add(container);
+
+        window = new ModalWindow("taskWin");
+        window.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        window.setInitialHeight(WIN_HEIGHT);
+        window.setInitialWidth(WIN_WIDTH);
+        window.setCookieName(VIEW_TASK_WIN_COOKIE_NAME);
+        add(window);
+
+        ((Tasks) pageRef.getPage()).setWindowClosedCallback(window, container);
+
+        paginatorRows = prefMan.getPaginatorRows(getWebRequest(), 
Constants.PREF_SYNC_TASKS_PAGINATOR_ROWS);
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof 
AbstractSearchResultPanel.EventDataWrapper) {
+            ((AbstractSearchResultPanel.EventDataWrapper) 
event.getPayload()).getTarget().add(container);
+        }
+    }
+
+    protected void initTasksTable() {
+
+        table = Tasks.updateTaskTable(
+                getColumns(),
+                new TasksProvider<T>(restClient, paginatorRows, getId(), 
this.reference),
+                container,
+                0,
+                this.pageRef,
+                restClient);
+
+        Form paginatorForm = new Form("PaginatorForm");
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final DropDownChoice rowsChooser = new DropDownChoice("rowsChooser", 
new PropertyModel(this, "paginatorRows"),
+                prefMan.getPaginatorChoices());
+
+        rowsChooser.add(new 
AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                prefMan.set(getWebRequest(), (WebResponse) getResponse(), 
Constants.PREF_SYNC_TASKS_PAGINATOR_ROWS,
+                        String.valueOf(paginatorRows));
+
+                table = Tasks.updateTaskTable(
+                        getColumns(),
+                        new TasksProvider<T>(restClient, paginatorRows, 
getId(), reference),
+                        container,
+                        table == null ? 0 : (int) table.getCurrentPage(),
+                        pageRef,
+                        restClient);
+
+                target.add(container);
+            }
+        });
+
+        paginatorForm.add(rowsChooser);
+        container.add(paginatorForm);
+
+        // create new task
+        AjaxLink<Void> createLink = new 
ClearIndicatingAjaxLink<Void>("createLink", pageRef) {
+
+            private static final long serialVersionUID = -7978723352517770644L;
+
+            @Override
+            protected void onClickInternal(final AjaxRequestTarget target) {
+                window.setPageCreator(new ModalWindow.PageCreator() {
+
+                    private static final long serialVersionUID = 
-7834632442532690940L;
+
+                    @Override
+                    public Page createPage() {
+                        return reference.equals(SyncTaskTO.class)
+                                ? new SyncTaskModalPage(window, new 
SyncTaskTO(), pageRef)
+                                : new PushTaskModalPage(window, new 
PushTaskTO(), pageRef);
+                    }
+                });
+
+                window.show(target);
+            }
+        };
+
+        MetaDataRoleAuthorizationStrategy.authorize(
+                createLink, RENDER, xmlRolesReader.getEntitlement(TASKS, 
"create"));
+
+        add(createLink);
+
+    }
+
+    protected abstract List<IColumn<AbstractTaskTO, String>> getColumns();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchPanel.java
----------------------------------------------------------------------
diff --git 
a/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchPanel.java
 
b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchPanel.java
new file mode 100644
index 0000000..d610739
--- /dev/null
+++ 
b/client/console/src/main/java/org/apache/syncope/client/console/panels/AbstractSearchPanel.java
@@ -0,0 +1,401 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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.client.console.rest.AuthRestClient;
+import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.console.rest.SchemaRestClient;
+import org.apache.syncope.common.lib.search.RoleFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.search.SearchableFields;
+import org.apache.syncope.common.lib.search.SpecialAttr;
+import org.apache.syncope.common.lib.search.SyncopeFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.search.SyncopeProperty;
+import org.apache.syncope.common.lib.search.UserFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.SubjectType;
+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.spring.injection.annot.SpringBean;
+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);
+
+    @SpringBean
+    protected SchemaRestClient schemaRestClient;
+
+    @SpringBean
+    protected ResourceRestClient resourceRestClient;
+
+    @SpringBean
+    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>> roleNames;
+
+    protected NotificationPanel searchFeedback;
+
+    protected List<SearchClause> searchClauses;
+
+    protected WebMarkupContainer searchFormContainer;
+
+    protected AttributableType attributableType;
+
+    protected boolean required;
+
+    protected AbstractSearchPanel(final String id, final AttributableType 
attributableType) {
+        this(id, attributableType, null, true);
+    }
+
+    protected AbstractSearchPanel(final String id, final AttributableType 
attributableType,
+            final String fiql, final boolean required) {
+
+        super(id);
+        populate();
+
+        this.attributableType = attributableType;
+        this.required = required;
+
+        setOutputMarkupId(true);
+
+        searchFormContainer = new WebMarkupContainer("searchFormContainer");
+        searchFormContainer.setOutputMarkupId(true);
+
+        searchFeedback = new NotificationPanel("searchFeedback", 
"notificationpanel_top_right",
+                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);
+
+        this.searchClauses = new ArrayList<>();
+        this.searchClauses.add(new SearchClause());
+        if (StringUtils.isNotBlank(fiql)) {
+            try {
+                FiqlParser<SearchBean> fiqlParser = new FiqlParser<>(
+                        SearchBean.class, 
SyncopeFiqlSearchConditionBuilder.CONTEXTUAL_PROPERTIES);
+                List<SearchClause> parsed = 
getSearchClauses(fiqlParser.parse(fiql));
+
+                this.searchClauses.clear();
+                this.searchClauses.addAll(parsed);
+            } catch (Exception e) {
+                LOG.error("Unparseable FIQL expression '{}'", fiql, e);
+            }
+        }
+
+        searchFormContainer.add(new SearchView("searchView", searchClauses, 
searchFormContainer, required,
+                types, anames, dnames, roleNames, resourceNames, 
entitlements));
+        add(searchFormContainer);
+    }
+
+    protected void populate() {
+        dnames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                return SearchableFields.get(
+                        attributableType == AttributableType.USER ? 
SubjectType.USER : SubjectType.ROLE);
+            }
+        };
+
+        anames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                return schemaRestClient.getPlainSchemaNames(attributableType);
+            }
+        };
+
+        resourceNames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                List<ResourceTO> resourceTOs = resourceRestClient.getAll();
+
+                List<String> result = new ArrayList<>(resourceTOs.size());
+
+                for (ResourceTO resource : resourceTOs) {
+                    result.add(resource.getKey());
+                }
+
+                return result;
+            }
+        };
+
+        entitlements = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                List<String> result = authRestClient.getOwnedEntitlements();
+                Collections.sort(result);
+                return result;
+            }
+        };
+    }
+
+    public NotificationPanel getSearchFeedback() {
+        return searchFeedback;
+    }
+
+    private SearchClause getPrimitiveSearchClause(final 
SearchCondition<SearchBean> sc) {
+        SearchClause searchClause = new SearchClause();
+
+        String property = sc.getCondition().getKeySet().iterator().next();
+        searchClause.setProperty(property);
+        String value = sc.getCondition().get(property);
+        searchClause.setValue(value);
+
+        if (SpecialAttr.ROLES.toString().equals(property)) {
+            searchClause.setType(SearchClause.Type.MEMBERSHIP);
+            for (String label : roleNames.getObject()) {
+                if (value.equals(label.substring(0, label.indexOf(' ')))) {
+                    searchClause.setProperty(label);
+                }
+            }
+        } else if (SpecialAttr.RESOURCES.toString().equals(property)) {
+            searchClause.setType(SearchClause.Type.RESOURCE);
+        } else if (SpecialAttr.ENTITLEMENTS.toString().equals(property)) {
+            searchClause.setType(SearchClause.Type.ENTITLEMENT);
+        } else {
+            searchClause.setType(SearchClause.Type.ATTRIBUTE);
+        }
+
+        switch (sc.getConditionType()) {
+            case EQUALS:
+                
searchClause.setComparator(SpecialAttr.NULL.toString().equals(value)
+                        ? SearchClause.Comparator.IS_NULL : 
SearchClause.Comparator.EQUALS);
+                break;
+
+            case NOT_EQUALS:
+                
searchClause.setComparator(SpecialAttr.NULL.toString().equals(value)
+                        ? SearchClause.Comparator.IS_NOT_NULL : 
SearchClause.Comparator.NOT_EQUALS);
+                break;
+
+            case GREATER_OR_EQUALS:
+                
searchClause.setComparator(SearchClause.Comparator.GREATER_OR_EQUALS);
+                break;
+
+            case GREATER_THAN:
+                
searchClause.setComparator(SearchClause.Comparator.GREATER_THAN);
+                break;
+
+            case LESS_OR_EQUALS:
+                
searchClause.setComparator(SearchClause.Comparator.LESS_OR_EQUALS);
+                break;
+
+            case LESS_THAN:
+                searchClause.setComparator(SearchClause.Comparator.LESS_THAN);
+                break;
+
+            default:
+                break;
+        }
+
+        return searchClause;
+    }
+
+    private List<SearchClause> getCompoundSearchClause(final 
SearchCondition<SearchBean> sc) {
+        List<SearchClause> clauses = new ArrayList<SearchClause>();
+
+        for (SearchCondition<SearchBean> searchCondition : 
sc.getSearchConditions()) {
+            if (searchCondition.getStatement() == null) {
+                clauses.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);
+                }
+                clauses.add(clause);
+            }
+        }
+
+        return clauses;
+    }
+
+    private List<SearchClause> getSearchClauses(final 
SearchCondition<SearchBean> sc) {
+        List<SearchClause> clauses = new ArrayList<SearchClause>();
+
+        if (sc.getStatement() == null) {
+            clauses.addAll(getCompoundSearchClause(sc));
+        } else {
+            clauses.add(getPrimitiveSearchClause(sc));
+        }
+
+        return clauses;
+    }
+
+    protected abstract SyncopeFiqlSearchConditionBuilder 
getSearchConditionBuilder();
+
+    public String buildFIQL() {
+        LOG.debug("Generating FIQL from List<SearchClause>: {}", 
searchClauses);
+
+        if (searchClauses.isEmpty() || searchClauses.get(0).getType() == null) 
{
+            return StringUtils.EMPTY;
+        }
+
+        SyncopeFiqlSearchConditionBuilder builder = 
getSearchConditionBuilder();
+
+        CompleteCondition prevCondition;
+        CompleteCondition condition = null;
+        for (int i = 0; i < searchClauses.size(); i++) {
+            prevCondition = condition;
+
+            switch (searchClauses.get(i).getType()) {
+                case ENTITLEMENT:
+                    condition = searchClauses.get(i).getComparator() == 
SearchClause.Comparator.EQUALS
+                            ? ((RoleFiqlSearchConditionBuilder) builder).
+                            hasEntitlements(searchClauses.get(i).getProperty())
+                            : ((RoleFiqlSearchConditionBuilder) builder).
+                            
hasNotEntitlements(searchClauses.get(i).getProperty());
+                    break;
+
+                case MEMBERSHIP:
+                    Long roleId = 
NumberUtils.toLong(searchClauses.get(i).getProperty().split(" ")[0]);
+                    condition = searchClauses.get(i).getComparator() == 
SearchClause.Comparator.EQUALS
+                            ? ((UserFiqlSearchConditionBuilder) 
builder).hasRoles(roleId)
+                            : ((UserFiqlSearchConditionBuilder) 
builder).hasNotRoles(roleId);
+                    break;
+
+                case RESOURCE:
+                    condition = searchClauses.get(i).getComparator() == 
SearchClause.Comparator.EQUALS
+                            ? 
builder.hasResources(searchClauses.get(i).getProperty())
+                            : 
builder.hasNotResources(searchClauses.get(i).getProperty());
+                    break;
+
+                case ATTRIBUTE:
+                    SyncopeProperty property = 
builder.is(searchClauses.get(i).getProperty());
+                    switch (searchClauses.get(i).getComparator()) {
+                        case IS_NULL:
+                            condition = 
builder.isNull(searchClauses.get(i).getProperty());
+                            break;
+
+                        case IS_NOT_NULL:
+                            condition = 
builder.isNotNull(searchClauses.get(i).getProperty());
+                            break;
+
+                        case LESS_THAN:
+                            condition = 
StringUtils.isNumeric(searchClauses.get(i).getProperty())
+                                    ? 
property.lessThan(NumberUtils.toDouble(searchClauses.get(i).getValue()))
+                                    : 
property.lexicalBefore(searchClauses.get(i).getValue());
+                            break;
+
+                        case LESS_OR_EQUALS:
+                            condition = 
StringUtils.isNumeric(searchClauses.get(i).getProperty())
+                                    ? 
property.lessOrEqualTo(NumberUtils.toDouble(searchClauses.get(i).getValue()))
+                                    : 
property.lexicalNotAfter(searchClauses.get(i).getValue());
+                            break;
+
+                        case GREATER_THAN:
+                            condition = 
StringUtils.isNumeric(searchClauses.get(i).getProperty())
+                                    ? 
property.greaterThan(NumberUtils.toDouble(searchClauses.get(i).getValue()))
+                                    : 
property.lexicalAfter(searchClauses.get(i).getValue());
+                            break;
+
+                        case GREATER_OR_EQUALS:
+                            condition = 
StringUtils.isNumeric(searchClauses.get(i).getProperty())
+                                    ? 
property.greaterOrEqualTo(NumberUtils.toDouble(searchClauses.get(i).getValue()))
+                                    : 
property.lexicalNotBefore(searchClauses.get(i).getValue());
+                            break;
+
+                        case NOT_EQUALS:
+                            condition = 
property.notEqualTo(searchClauses.get(i).getValue());
+                            break;
+
+                        case EQUALS:
+                        default:
+                            condition = 
property.equalTo(searchClauses.get(i).getValue());
+                            break;
+                    }
+                default:
+                    break;
+            }
+
+            if (i > 0) {
+                if (searchClauses.get(i).getOperator() == 
SearchClause.Operator.AND) {
+                    condition = builder.and(prevCondition, condition);
+                }
+                if (searchClauses.get(i).getOperator() == 
SearchClause.Operator.OR) {
+                    condition = builder.or(prevCondition, condition);
+                }
+            }
+        }
+
+        String fiql = condition == null ? StringUtils.EMPTY : 
condition.query();
+        LOG.debug("Generated FIQL: {}", fiql);
+        return fiql;
+    }
+}

Reply via email to