syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From fmarte...@apache.org
Subject [3/4] syncope git commit: [SYNCOPE-745] Provides notification and email template management. Still missing tests and tasks per notification.
Date Fri, 08 Apr 2016 16:30:14 GMT
http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
new file mode 100644
index 0000000..037773d
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationWizardBuilder.java
@@ -0,0 +1,435 @@
+/*
+ * 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.notifications;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.collections4.Transformer;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.search.AbstractSearchPanel;
+import org.apache.syncope.client.console.panels.search.AnyObjectSearchPanel;
+import org.apache.syncope.client.console.panels.search.GroupSearchPanel;
+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.LoggerRestClient;
+import org.apache.syncope.client.console.rest.NotificationRestClient;
+import org.apache.syncope.client.console.rest.SchemaRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
+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.MultiFieldPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.MultiPanel;
+import org.apache.syncope.client.console.wizards.AjaxWizardBuilder;
+import org.apache.syncope.common.lib.EntityTOUtils;
+import org.apache.syncope.common.lib.to.AnyTypeTO;
+import org.apache.syncope.common.lib.to.DerSchemaTO;
+import org.apache.syncope.common.lib.to.MailTemplateTO;
+import org.apache.syncope.common.lib.to.NotificationTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.common.lib.types.TraceLevel;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.extensions.wizard.WizardModel;
+import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.util.ListModel;
+import org.apache.wicket.validation.validator.EmailAddressValidator;
+
+public class NotificationWizardBuilder extends AjaxWizardBuilder<NotificationHandler> {
+
+    private static final long serialVersionUID = -1975312550059578553L;
+
+    private final NotificationRestClient restClient = new NotificationRestClient();
+
+    private final SchemaRestClient schemaRestClient = new SchemaRestClient();
+
+    private final LoggerRestClient loggerRestClient = new LoggerRestClient();
+
+    /**
+     * Construct.
+     *
+     * @param notificationTO notification.
+     * @param pageRef Caller page reference.
+     */
+    public NotificationWizardBuilder(final NotificationTO notificationTO, final PageReference pageRef) {
+        super(new NotificationHandler(notificationTO), pageRef);
+    }
+
+    @Override
+    protected Serializable onApplyInternal(final NotificationHandler modelObject) {
+        modelObject.fillRecipientConditions();
+        modelObject.fillAboutConditions();
+
+        final boolean createFlag
+                = modelObject.getInnerObject().getKey() == null || modelObject.getInnerObject().getKey() <= 0;
+
+        if (createFlag) {
+            restClient.create(modelObject.getInnerObject());
+        } else {
+            restClient.update(modelObject.getInnerObject());
+        }
+
+        return null;
+    }
+
+    @Override
+    protected WizardModel buildModelSteps(final NotificationHandler modelObject, final WizardModel wizardModel) {
+        wizardModel.add(new NotificationWizardBuilder.Details(modelObject));
+        wizardModel.add(new NotificationWizardBuilder.Events(modelObject));
+        wizardModel.add(new NotificationWizardBuilder.Abouts(modelObject));
+        wizardModel.add(new NotificationWizardBuilder.Recipients(modelObject));
+        return wizardModel;
+    }
+
+    public class Details extends WizardStep {
+
+        private static final long serialVersionUID = -7709805590497687958L;
+
+        public Details(final NotificationHandler modelObject) {
+            final NotificationTO notificationTO = modelObject.getInnerObject();
+            final boolean createFlag = notificationTO.getKey() == null || notificationTO.getKey() <= 0;
+
+            final AjaxTextFieldPanel sender = new AjaxTextFieldPanel("sender", getString("sender"),
+                    new PropertyModel<String>(notificationTO, "sender"));
+            sender.addRequiredLabel();
+            sender.addValidator(EmailAddressValidator.getInstance());
+            add(sender);
+
+            final AjaxTextFieldPanel subject = new AjaxTextFieldPanel("subject", getString("subject"),
+                    new PropertyModel<String>(notificationTO, "subject"));
+            subject.addRequiredLabel();
+            add(subject);
+
+            final AjaxDropDownChoicePanel<IntMappingType> recipientAttrType
+                    = new AjaxDropDownChoicePanel<IntMappingType>(
+                            "recipientAttrType",
+                            new ResourceModel("recipientAttrType", "recipientAttrType").getObject(),
+                            new PropertyModel<IntMappingType>(notificationTO, "recipientAttrType"));
+            recipientAttrType.setChoices(
+                    new ArrayList<IntMappingType>(IntMappingType.getAttributeTypes(AnyTypeKind.USER,
+                            EnumSet.of(IntMappingType.UserKey, IntMappingType.Password))));
+            recipientAttrType.addRequiredLabel();
+            add(recipientAttrType);
+
+            final AjaxDropDownChoicePanel<String> recipientAttrName = new AjaxDropDownChoicePanel<String>(
+                    "recipientAttrName", new ResourceModel("recipientAttrName", "recipientAttrName").getObject(),
+                    new PropertyModel<String>(notificationTO, "recipientAttrName"));
+            recipientAttrName.setChoices(getSchemaNames(recipientAttrType.getModelObject()));
+            recipientAttrName.addRequiredLabel();
+            add(recipientAttrName);
+
+            recipientAttrType.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                private static final long serialVersionUID = -1107858522700306810L;
+
+                @Override
+                protected void onUpdate(final AjaxRequestTarget target) {
+                    recipientAttrName.setChoices(getSchemaNames(recipientAttrType.getModelObject()));
+                    target.add(recipientAttrName);
+                }
+            });
+
+            final AjaxDropDownChoicePanel<String> template = new AjaxDropDownChoicePanel<String>(
+                    "template", getString("template"),
+                    new PropertyModel<String>(notificationTO, "template"));
+
+            template.setChoices(CollectionUtils.collect(
+                    restClient.getAllAvailableTemplates(), new Transformer<MailTemplateTO, String>() {
+
+                @Override
+                public String transform(final MailTemplateTO input) {
+                    return input.getKey();
+                }
+            }, new ArrayList<String>()));
+
+            template.addRequiredLabel();
+            add(template);
+
+            final AjaxDropDownChoicePanel<TraceLevel> traceLevel = new AjaxDropDownChoicePanel<TraceLevel>(
+                    "traceLevel", getString("traceLevel"),
+                    new PropertyModel<TraceLevel>(notificationTO, "traceLevel"));
+            traceLevel.setChoices(Arrays.asList(TraceLevel.values()));
+            traceLevel.addRequiredLabel();
+            add(traceLevel);
+
+            final AjaxCheckBoxPanel isActive = new AjaxCheckBoxPanel("isActive",
+                    getString("isActive"), new PropertyModel<Boolean>(notificationTO, "active"));
+
+            if (createFlag) {
+                isActive.getField().setDefaultModelObject(Boolean.TRUE);
+            }
+            add(isActive);
+        }
+
+    }
+
+    public class Events extends WizardStep {
+
+        private static final long serialVersionUID = -7709805590497687958L;
+
+        public Events(final NotificationHandler modelObject) {
+            final NotificationTO notificationTO = modelObject.getInnerObject();
+            add(new LoggerCategoryPanel(
+                    "eventSelection",
+                    loggerRestClient.listEvents(),
+                    new PropertyModel<List<String>>(notificationTO, "events"),
+                    pageRef,
+                    "Notification") {
+
+                private static final long serialVersionUID = 6429053774964787735L;
+
+                @Override
+                protected String[] getListRoles() {
+                    return new String[] {};
+                }
+
+                @Override
+                protected String[] getChangeRoles() {
+                    return new String[] {};
+                }
+            });
+        }
+
+    }
+
+    public class About extends Panel {
+
+        private static final long serialVersionUID = -9149543787708482882L;
+
+        public About(final String id, final IModel<Pair<String, List<SearchClause>>> model) {
+            super(id, model);
+            setOutputMarkupId(true);
+
+            final List<String> anyTypeTOs = CollectionUtils.collect(
+                    new AnyTypeRestClient().list(),
+                    EntityTOUtils.<String, AnyTypeTO>keyTransformer(),
+                    new ArrayList<String>());
+
+            final AjaxDropDownChoicePanel<String> type
+                    = new AjaxDropDownChoicePanel<String>("about", "about", new Model<String>() {
+
+                        private static final long serialVersionUID = -2350296434572623272L;
+
+                        @Override
+                        public String getObject() {
+                            return model.getObject().getLeft();
+                        }
+
+                        @Override
+                        public void setObject(final String object) {
+                            model.setObject(Pair.of(object, model.getObject().getRight()));
+                        }
+
+                    });
+            type.setChoices(anyTypeTOs);
+            type.addRequiredLabel();
+            add(type);
+
+            final ListModel<SearchClause> clauseModel = new ListModel<SearchClause>() {
+
+                private static final long serialVersionUID = 3769540249683319782L;
+
+                @Override
+                public List<SearchClause> getObject() {
+                    return model.getObject().getRight();
+                }
+
+                @Override
+                public void setObject(final List<SearchClause> object) {
+                    model.getObject().setValue(object);
+                }
+
+            };
+
+            final WebMarkupContainer searchContainer = new WebMarkupContainer("search");
+            add(searchContainer.setOutputMarkupId(true));
+
+            searchContainer.add(getClauseBuilder(model.getObject().getLeft(), clauseModel).build("clauses"));
+
+            type.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                private static final long serialVersionUID = -1107858522700306810L;
+
+                @Override
+                protected void onUpdate(final AjaxRequestTarget target) {
+                    clauseModel.getObject().clear();
+                    searchContainer.addOrReplace(getClauseBuilder(type.getModelObject(), clauseModel).build("clauses").
+                            setRenderBodyOnly(true));
+                    target.add(searchContainer);
+                }
+            });
+        }
+
+        private AbstractSearchPanel.Builder<?> getClauseBuilder(
+                final String type, final ListModel<SearchClause> clauseModel) {
+            AbstractSearchPanel.Builder<?> clause;
+
+            switch (type) {
+                case "USER":
+                    clause = new UserSearchPanel.Builder(clauseModel);
+                    break;
+                case "GROUP":
+                    clause = new GroupSearchPanel.Builder(clauseModel);
+                    break;
+                default:
+                    clause = new AnyObjectSearchPanel.Builder(type, clauseModel);
+                    break;
+            }
+            return clause;
+        }
+
+    }
+
+    public class Abouts extends WizardStep {
+
+        private static final long serialVersionUID = -7709805590497687958L;
+
+        public Abouts(final NotificationHandler modelObject) {
+            final WebMarkupContainer aboutContainer = new WebMarkupContainer("about");
+            aboutContainer.setOutputMarkupId(true);
+            add(aboutContainer);
+
+            final IModel<List<Pair<String, List<SearchClause>>>> model
+                    = new PropertyModel<List<Pair<String, List<SearchClause>>>>(modelObject, "aboutClauses");
+
+            aboutContainer.add(new MultiPanel<Pair<String, List<SearchClause>>>("abouts", "abouts", model, false) {
+
+                private static final long serialVersionUID = -2481579077338205547L;
+
+                @Override
+                protected Pair<String, List<SearchClause>> newModelObject() {
+                    return Pair.<String, List<SearchClause>>of(AnyTypeKind.USER.name(), new ArrayList<SearchClause>());
+                }
+
+                @Override
+                protected About getItemPanel(final ListItem<Pair<String, List<SearchClause>>> item) {
+
+                    return new About("panel", new Model<Pair<String, List<SearchClause>>>() {
+
+                        private static final long serialVersionUID = 6799404673615637845L;
+
+                        @Override
+                        public Pair<String, List<SearchClause>> getObject() {
+                            return item.getModelObject();
+                        }
+
+                        @Override
+                        public void setObject(final Pair<String, List<SearchClause>> object) {
+                            item.setModelObject(object);
+                        }
+
+                        @Override
+                        public void detach() {
+                            // no detach
+                        }
+                    });
+                }
+            }.hideLabel());
+        }
+    }
+
+    public class Recipients extends WizardStep {
+
+        private static final long serialVersionUID = -7709805590497687958L;
+
+        public Recipients(final NotificationHandler modelObject) {
+            final NotificationTO notificationTO = modelObject.getInnerObject();
+            final boolean createFlag = notificationTO.getKey() == null || notificationTO.getKey() <= 0;
+
+            final AjaxTextFieldPanel staticRecipientsFieldPanel
+                    = new AjaxTextFieldPanel("panel", "staticRecipients", new Model<String>());
+            staticRecipientsFieldPanel.addValidator(EmailAddressValidator.getInstance());
+
+            final MultiFieldPanel<String> staticRecipients = new MultiFieldPanel.Builder<String>(
+                    new PropertyModel<List<String>>(notificationTO, "staticRecipients")).
+                    build("staticRecipients", "staticRecipients", staticRecipientsFieldPanel);
+
+            add(staticRecipients.hideLabel());
+
+            final AnyObjectSearchPanel recipients = new UserSearchPanel.Builder(
+                    new PropertyModel<List<SearchClause>>(modelObject, "recipientClauses")).
+                    required(false).build("recipients");
+            add(recipients);
+
+            final AjaxCheckBoxPanel selfAsRecipient = new AjaxCheckBoxPanel("selfAsRecipient",
+                    getString("selfAsRecipient"), new PropertyModel<Boolean>(notificationTO, "selfAsRecipient"));
+            add(selfAsRecipient);
+
+            if (createFlag) {
+                selfAsRecipient.getField().setDefaultModelObject(Boolean.FALSE);
+            }
+        }
+
+    }
+
+    private List<String> getSchemaNames(final IntMappingType type) {
+        final List<String> result;
+
+        if (type == null) {
+            result = Collections.<String>emptyList();
+        } else {
+            switch (type) {
+                case UserPlainSchema:
+                    result = CollectionUtils.collect(
+                            schemaRestClient.<PlainSchemaTO>getSchemas(SchemaType.PLAIN, AnyTypeKind.USER.name()),
+                            EntityTOUtils.<String, PlainSchemaTO>keyTransformer(), new ArrayList<String>());
+                    break;
+
+                case UserDerivedSchema:
+                    result = CollectionUtils.collect(
+                            schemaRestClient.<DerSchemaTO>getSchemas(SchemaType.DERIVED, AnyTypeKind.USER.name()),
+                            EntityTOUtils.<String, DerSchemaTO>keyTransformer(), new ArrayList<String>());
+                    break;
+
+                case UserVirtualSchema:
+                    result = CollectionUtils.collect(
+                            schemaRestClient.<VirSchemaTO>getSchemas(SchemaType.VIRTUAL, AnyTypeKind.USER.name()),
+                            EntityTOUtils.<String, VirSchemaTO>keyTransformer(), new ArrayList<String>());
+                    break;
+
+                case Username:
+                    result = Collections.singletonList("Username");
+                    break;
+
+                default:
+                    result = Collections.<String>emptyList();
+            }
+        }
+
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/SelectedEventsPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/SelectedEventsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/SelectedEventsPanel.java
new file mode 100644
index 0000000..cf396f6
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/SelectedEventsPanel.java
@@ -0,0 +1,172 @@
+/*
+ * 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.notifications;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.markup.html.form.ListMultipleChoice;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.util.ListModel;
+
+public class SelectedEventsPanel extends Panel {
+
+    private static final long serialVersionUID = -4832450230348213500L;
+
+    private final WebMarkupContainer selectionContainer;
+
+    private ListMultipleChoice<String> selectedEvents;
+
+    private final IModel<List<String>> model;
+
+    public SelectedEventsPanel(final String id, final IModel<List<String>> model) {
+        super(id);
+
+        this.model = model;
+
+        selectionContainer = new WebMarkupContainer("selectionContainer");
+        selectionContainer.setOutputMarkupId(true);
+        add(selectionContainer);
+
+        selectedEvents = new ListMultipleChoice<String>("selectedEvents", new ListModel<String>(), model) {
+
+            private static final long serialVersionUID = 1226677544225737338L;
+
+            @Override
+            protected void onComponentTag(final ComponentTag tag) {
+                super.onComponentTag(tag);
+                tag.remove("size");
+                tag.remove("multiple");
+                tag.put("size", 5);
+            }
+        };
+
+        selectedEvents.setMaxRows(5);
+        selectedEvents.setChoiceRenderer(new IChoiceRenderer<String>() {
+
+            private static final long serialVersionUID = -4288397951948436434L;
+
+            @Override
+            public Object getDisplayValue(final String object) {
+                return object;
+            }
+
+            @Override
+            public String getIdValue(final String object, final int index) {
+                return object;
+            }
+
+            @Override
+            public String getObject(final String id, final IModel<? extends List<? extends String>> choices) {
+                return id;
+            }
+        });
+
+        selectedEvents.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -151291731388673682L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                send(SelectedEventsPanel.this.getPage(),
+                        Broadcast.BREADTH,
+                        new InspectSelectedEvent(target, selectedEvents.getModelValue()));
+            }
+        });
+
+        selectionContainer.add(selectedEvents);
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof EventSelectionChanged) {
+            final EventSelectionChanged eventSelectionChanged = (EventSelectionChanged) event.getPayload();
+
+            for (String toBeRemoved : eventSelectionChanged.getToBeRemoved()) {
+                model.getObject().remove(toBeRemoved);
+            }
+
+            for (String toBeAdded : eventSelectionChanged.getToBeAdded()) {
+                if (!model.getObject().contains(toBeAdded)) {
+                    model.getObject().add(toBeAdded);
+                }
+            }
+
+            eventSelectionChanged.getTarget().add(selectionContainer);
+        }
+    }
+
+    public static class InspectSelectedEvent {
+
+        private final AjaxRequestTarget target;
+
+        private final String event;
+
+        public InspectSelectedEvent(final AjaxRequestTarget target, final String event) {
+            this.target = target;
+            this.event = event;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
+        public String getEvent() {
+            return event;
+        }
+    }
+
+    public static class EventSelectionChanged {
+
+        private final AjaxRequestTarget target;
+
+        private final Set<String> toBeRemoved;
+
+        private final Set<String> toBeAdded;
+
+        public EventSelectionChanged(
+                final AjaxRequestTarget target,
+                final Set<String> toBeAdded,
+                final Set<String> toBeRemoved) {
+            this.target = target;
+            this.toBeAdded = toBeAdded;
+            this.toBeRemoved = toBeRemoved;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
+        public Set<String> getToBeRemoved() {
+            return toBeRemoved;
+        }
+
+        public Set<String> getToBeAdded() {
+            return toBeAdded;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/pages/Notifications.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Notifications.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Notifications.java
index b1b7ee9..c09657c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Notifications.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Notifications.java
@@ -18,7 +18,17 @@
  */
 package org.apache.syncope.client.console.pages;
 
+import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbedPanel;
+import java.util.ArrayList;
+import java.util.List;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
+import org.apache.syncope.client.console.notifications.MailTemplateDirectoryPanel;
+import org.apache.syncope.client.console.notifications.NotificationDirectoryPanel;
+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.panel.Panel;
+import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 
 public class Notifications extends BasePage {
@@ -29,5 +39,36 @@ public class Notifications extends BasePage {
         super(parameters);
 
         body.add(BookmarkablePageLinkBuilder.build("dashboard", "dashboardBr", Dashboard.class));
+
+        WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        content.setMarkupId("notifications");
+        content.add(new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList()));
+        body.add(content);
+    }
+
+    private List<ITab> buildTabList() {
+        final List<ITab> tabs = new ArrayList<>();
+
+        tabs.add(new AbstractTab(new ResourceModel("notifications")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new NotificationDirectoryPanel(panelId, getPageReference());
+            }
+        });
+
+        tabs.add(new AbstractTab(new ResourceModel("notification.templates")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new MailTemplateDirectoryPanel(panelId, getPageReference());
+            }
+        });
+        return tabs;
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java
index 02ac0e3..3758258 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java
@@ -20,7 +20,6 @@ package org.apache.syncope.client.console.pages;
 
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.panels.RoleDirectoryPanel;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
 import org.apache.syncope.client.console.wizards.role.RoleHandler;
 import org.apache.syncope.client.console.wizards.role.RoleWizardBuilder;
@@ -46,8 +45,7 @@ public class Roles extends BasePage {
             private static final long serialVersionUID = -5960765294082359003L;
 
         }.disableCheckBoxes().addNewItemPanelBuilder(
-                new RoleWizardBuilder(BaseModal.CONTENT_ID, new RoleTO(), getPageReference()), true).
-                build("rolesPanel");
+                new RoleWizardBuilder(new RoleTO(), getPageReference()), true).build("rolesPanel");
 
         content.add(rolesPanel);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
index 3ff36bd..f6aeaa5 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyPanel.java
@@ -25,7 +25,6 @@ import org.apache.syncope.client.console.panels.search.SearchClausePanel;
 import org.apache.syncope.client.console.panels.search.SearchUtils;
 import org.apache.syncope.client.console.panels.search.UserSearchPanel;
 import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.tabs.Accordion;
 import org.apache.syncope.client.console.wizards.any.AnyObjectWizardBuilder;
 import org.apache.syncope.client.console.wizards.any.GroupWizardBuilder;
@@ -200,7 +199,7 @@ public class AnyPanel extends Panel {
                         anyTypeTO.getKey(),
                         pageRef).setRealm(realmTO.getFullPath()).setFiltered(true).
                         setFiql(fiql).addNewItemPanelBuilder(new UserWizardBuilder(
-                        BaseModal.CONTENT_ID, userTO, anyTypeTO.getClasses(), pageRef)).build(id);
+                        userTO, anyTypeTO.getClasses(), pageRef)).build(id);
                 MetaDataRoleAuthorizationStrategy.authorize(panel, WebPage.RENDER, StandardEntitlement.USER_LIST);
                 break;
             case GROUP:
@@ -212,7 +211,7 @@ public class AnyPanel extends Panel {
                         anyTypeTO.getKey(),
                         pageRef).setRealm(realmTO.getFullPath()).setFiltered(true).
                         setFiql(fiql).addNewItemPanelBuilder(new GroupWizardBuilder(
-                        BaseModal.CONTENT_ID, groupTO, anyTypeTO.getClasses(), pageRef)).build(id);
+                        groupTO, anyTypeTO.getClasses(), pageRef)).build(id);
                 // list of group is available to all authenticated users
                 break;
             case ANY_OBJECT:
@@ -226,7 +225,7 @@ public class AnyPanel extends Panel {
                         anyTypeTO.getKey(),
                         pageRef).setRealm(realmTO.getFullPath()).setFiltered(true).
                         setFiql(fiql).addNewItemPanelBuilder(new AnyObjectWizardBuilder(
-                        BaseModal.CONTENT_ID, anyObjectTO, anyTypeTO.getClasses(), pageRef)).build(id);
+                        anyObjectTO, anyTypeTO.getClasses(), pageRef)).build(id);
                 MetaDataRoleAuthorizationStrategy.authorize(
                         panel, WebPage.RENDER, AnyEntitlement.LIST.getFor(anyTypeTO.getKey()));
                 break;

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
index 03005f9..fdfbc65 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypeClassesPanel.java
@@ -33,7 +33,6 @@ import org.apache.syncope.client.console.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 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.client.console.wizards.AbstractModalPanelBuilder;
@@ -60,13 +59,12 @@ public class AnyTypeClassesPanel extends TypesDirectoryPanel<AnyTypeClassTO, Any
         super(id, pageRef);
         disableCheckBoxes();
 
-        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AnyTypeClassTO>(
-                BaseModal.CONTENT_ID, new AnyTypeClassTO(), pageRef) {
+        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AnyTypeClassTO>(new AnyTypeClassTO(), pageRef) {
 
             private static final long serialVersionUID = -6388405037134399367L;
 
             @Override
-            public ModalPanel<AnyTypeClassTO> build(final int index, final AjaxWizard.Mode mode) {
+            public ModalPanel<AnyTypeClassTO> build(final String id, final int index, final AjaxWizard.Mode mode) {
                 final AnyTypeClassTO modelObject = newModelObject();
                 return new AnyTypeClassModalPanel(modal, modelObject, pageRef) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
index f77654c..6b3c042 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/AnyTypesPanel.java
@@ -34,7 +34,6 @@ import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
 import org.apache.syncope.client.console.panels.AnyTypesPanel.AnyTypeProvider;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 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.client.console.wizards.AbstractModalPanelBuilder;
@@ -61,13 +60,12 @@ public class AnyTypesPanel extends TypesDirectoryPanel<AnyTypeTO, AnyTypeProvide
         super(id, pageRef);
         disableCheckBoxes();
 
-        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AnyTypeTO>(
-                BaseModal.CONTENT_ID, new AnyTypeTO(), pageRef) {
+        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AnyTypeTO>(new AnyTypeTO(), pageRef) {
 
             private static final long serialVersionUID = -6388405037134399367L;
 
             @Override
-            public ModalPanel<AnyTypeTO> build(final int index, final AjaxWizard.Mode mode) {
+            public ModalPanel<AnyTypeTO> build(final String id, final int index, final AjaxWizard.Mode mode) {
                 final AnyTypeTO modelObject = newModelObject();
                 return new AnyTypeModalPanel(modal, modelObject, pageRef) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateModalPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateModalPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateModalPanel.java
index 6e71a2b..87eeee3 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateModalPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateModalPanel.java
@@ -35,8 +35,8 @@ public class ParametersCreateModalPanel extends AbstractModalPanel<AttrTO> {
             final PageReference pageRef) {
         super(modal, pageRef);
         this.attrTO = attrTO;
-        add(new ParametersCreateWizardPanel("parametersCreateWizardPanel",
-                new ParametersCreateWizardPanel.ParametersForm(), pageRef).build(AjaxWizard.Mode.CREATE));
+        add(new ParametersCreateWizardPanel(new ParametersCreateWizardPanel.ParametersForm(), pageRef).
+                build("parametersCreateWizardPanel", AjaxWizard.Mode.CREATE));
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java
index 060160e..eace16b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersCreateWizardPanel.java
@@ -37,8 +37,8 @@ public class ParametersCreateWizardPanel extends AjaxWizardBuilder<ParametersCre
 
     private static final Logger LOG = LoggerFactory.getLogger(ParametersCreateWizardPanel.class);
 
-    public ParametersCreateWizardPanel(final String id, final ParametersForm defaultItem, final PageReference pageRef) {
-        super(id, defaultItem, pageRef);
+    public ParametersCreateWizardPanel(final ParametersForm defaultItem, final PageReference pageRef) {
+        super(defaultItem, pageRef);
 
     }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersPanel.java
index 67da08d..df265fd 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ParametersPanel.java
@@ -95,13 +95,12 @@ public class ParametersPanel extends DirectoryPanel<
 
         addInnerObject(modalDetails);
 
-        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AttrTO>(
-                BaseModal.CONTENT_ID, new AttrTO(), pageRef) {
+        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AttrTO>(new AttrTO(), pageRef) {
 
             private static final long serialVersionUID = 1995192603527154740L;
 
             @Override
-            public ModalPanel<AttrTO> build(final int index, final AjaxWizard.Mode mode) {
+            public ModalPanel<AttrTO> build(final String id, final int index, final AjaxWizard.Mode mode) {
                 return new ParametersCreateModalPanel(modal, newModelObject(), pageRef);
             }
         }, true);

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
index eea61bf..314ce42 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/RelationshipTypesPanel.java
@@ -34,7 +34,6 @@ import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
 import org.apache.syncope.client.console.panels.RelationshipTypesPanel.RelationshipTypeProvider;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 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.client.console.wizards.AbstractModalPanelBuilder;
@@ -61,13 +60,13 @@ public class RelationshipTypesPanel extends TypesDirectoryPanel<RelationshipType
         super(id, pageRef);
         disableCheckBoxes();
 
-        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<RelationshipTypeTO>(
-                BaseModal.CONTENT_ID, new RelationshipTypeTO(), pageRef) {
+        this.addNewItemPanelBuilder(
+                new AbstractModalPanelBuilder<RelationshipTypeTO>(new RelationshipTypeTO(), pageRef) {
 
             private static final long serialVersionUID = -6388405037134399367L;
 
             @Override
-            public ModalPanel<RelationshipTypeTO> build(final int index, final AjaxWizard.Mode mode) {
+            public ModalPanel<RelationshipTypeTO> build(final String id, final int index, final AjaxWizard.Mode mode) {
                 final RelationshipTypeTO modelObject = newModelObject();
                 return new RelationshipTypeModalPanel(modal, modelObject, pageRef) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceModal.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceModal.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceModal.java
index 12a19d0..11c7468 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceModal.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceModal.java
@@ -92,8 +92,8 @@ public class ResourceModal<T extends Serializable> extends AbstractResourceModal
         //--------------------------------
         // Resource provision panels
         //--------------------------------
-        final ListViewPanel.Builder<ProvisionTO> builder =
-                new ListViewPanel.Builder<ProvisionTO>(ProvisionTO.class, pageRef) {
+        final ListViewPanel.Builder<ProvisionTO> builder = new ListViewPanel.Builder<ProvisionTO>(ProvisionTO.class,
+                pageRef) {
 
             private static final long serialVersionUID = 4907732721283972943L;
 
@@ -163,7 +163,7 @@ public class ResourceModal<T extends Serializable> extends AbstractResourceModal
                     }
                 }, ActionLink.ActionType.DELETE, StandardEntitlement.RESOURCE_DELETE);
 
-        builder.addNewItemPanelBuilder(new ProvisionWizardBuilder("wizard", model.getObject(), pageRef));
+        builder.addNewItemPanelBuilder(new ProvisionWizardBuilder(model.getObject(), pageRef));
 
         tabs.add(new AbstractTab(new ResourceModel("provisions")) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
index bd3b18d..7d61e47 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypePanel.java
@@ -38,7 +38,6 @@ import org.apache.syncope.client.console.panels.SchemaTypePanel.SchemaProvider;
 import org.apache.syncope.client.console.rest.SchemaRestClient;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.BooleanPropertyColumn;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 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.client.console.wizards.AbstractModalPanelBuilder;
@@ -89,13 +88,14 @@ public class SchemaTypePanel extends TypesDirectoryPanel<AbstractSchemaTO, Schem
         this.schemaType = schemaType;
 
         try {
-            this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<AbstractSchemaTO>(
-                    BaseModal.CONTENT_ID, schemaType.getToClass().newInstance(), pageRef) {
+            this.addNewItemPanelBuilder(
+                    new AbstractModalPanelBuilder<AbstractSchemaTO>(schemaType.getToClass().newInstance(), pageRef) {
 
                 private static final long serialVersionUID = -6388405037134399367L;
 
                 @Override
-                public ModalPanel<AbstractSchemaTO> build(final int index, final AjaxWizard.Mode mode) {
+                public ModalPanel<AbstractSchemaTO> build(
+                        final String id, final int index, final AjaxWizard.Mode mode) {
                     final AbstractSchemaTO modelObject = newModelObject();
                     return new SchemaModalPanel(modal, modelObject, pageRef) {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
index 4db7acf..38d81cf 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/SecurityQuestionsPanel.java
@@ -35,7 +35,6 @@ import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
 import org.apache.syncope.client.console.panels.SecurityQuestionsPanel.SecurityQuestionsProvider;
 import org.apache.syncope.client.console.rest.SecurityQuestionRestClient;
 import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 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.client.console.wizards.AbstractModalPanelBuilder;
@@ -70,13 +69,13 @@ public class SecurityQuestionsPanel extends DirectoryPanel<
             }
         }.disableCheckBoxes());
 
-        this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<SecurityQuestionTO>(
-                BaseModal.CONTENT_ID, new SecurityQuestionTO(), pageRef) {
+        this.addNewItemPanelBuilder(
+                new AbstractModalPanelBuilder<SecurityQuestionTO>(new SecurityQuestionTO(), pageRef) {
 
             private static final long serialVersionUID = -6388405037134399367L;
 
             @Override
-            public ModalPanel<SecurityQuestionTO> build(final int index, final AjaxWizard.Mode mode) {
+            public ModalPanel<SecurityQuestionTO> build(final String id, final int index, final AjaxWizard.Mode mode) {
                 final SecurityQuestionTO modelObject = newModelObject();
                 return new SecurityQuestionsModalPanel(modal, modelObject, pageRef);
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/rest/NotificationRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/NotificationRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/NotificationRestClient.java
index 053768a..3f9d820 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/NotificationRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/NotificationRestClient.java
@@ -18,14 +18,23 @@
  */
 package org.apache.syncope.client.console.rest;
 
+import java.io.InputStream;
 import java.util.List;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.to.MailTemplateTO;
 import org.apache.syncope.common.lib.to.NotificationTO;
+import org.apache.syncope.common.lib.types.MailTemplateFormat;
+import org.apache.syncope.common.rest.api.service.MailTemplateService;
 import org.apache.syncope.common.rest.api.service.NotificationService;
+import org.slf4j.LoggerFactory;
 
 public class NotificationRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = 6328933265096511690L;
 
+    protected static final org.slf4j.Logger LOG = LoggerFactory.getLogger(NotificationRestClient.class);
+
     public List<NotificationTO> getAllNotifications() {
         return getService(NotificationService.class).list();
     }
@@ -45,4 +54,34 @@ public class NotificationRestClient extends BaseRestClient {
     public void delete(final Long key) {
         getService(NotificationService.class).delete(key);
     }
+
+    public List<MailTemplateTO> getAllAvailableTemplates() {
+        return getService(MailTemplateService.class).list();
+    }
+
+    public void createTemplate(final MailTemplateTO mailTemplateTO) {
+        getService(MailTemplateService.class).create(mailTemplateTO);
+    }
+
+    public void deleteTemplate(final String key) {
+        getService(MailTemplateService.class).delete(key);
+    }
+
+    public MailTemplateTO readTemplate(final String key) {
+        return getService(MailTemplateService.class).read(key);
+    }
+
+    public String readTemplateFormat(final String key, final MailTemplateFormat format) {
+        try {
+            return IOUtils.toString(InputStream.class.cast(
+                    getService(MailTemplateService.class).getFormat(key, format).getEntity()));
+        } catch (Exception e) {
+            LOG.info("Error retrieving mail tenplate content");
+            return StringUtils.EMPTY;
+        }
+    }
+
+    public void updateTemplateFormat(final String key, final String str, final MailTemplateFormat format) {
+        getService(MailTemplateService.class).setFormat(key, format, IOUtils.toInputStream(str));
+    }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java
new file mode 100644
index 0000000..6309e74
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/NotificationTaskDirectoryPanel.java
@@ -0,0 +1,208 @@
+/*
+ * 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.tasks;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.TaskDataProvider;
+import org.apache.syncope.client.console.panels.ModalPanel;
+import org.apache.syncope.client.console.panels.MultilevelPanel;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.CollectionPropertyColumn;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.syncope.common.lib.types.TaskType;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.NotificationTaskTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+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.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
+
+/**
+ * Tasks page.
+ */
+public abstract class NotificationTaskDirectoryPanel
+        extends TaskDirectoryPanel<NotificationTaskTO> implements ModalPanel<NotificationTaskTO> {
+
+    private static final long serialVersionUID = 4984337552918213290L;
+
+    protected NotificationTaskDirectoryPanel(
+            final BaseModal<?> baseModal,
+            final MultilevelPanel multiLevelPanelRef,
+            final PageReference pageRef) {
+        super(baseModal, multiLevelPanelRef, pageRef);
+        initResultTable();
+    }
+
+    @Override
+    protected List<IColumn<NotificationTaskTO, String>> getColumns() {
+        final List<IColumn<NotificationTaskTO, String>> columns = new ArrayList<>();
+
+        columns.add(new PropertyColumn<NotificationTaskTO, String>(
+                new StringResourceModel("key", this, null), "key", "key"));
+
+        columns.add(new PropertyColumn<NotificationTaskTO, String>(
+                new StringResourceModel("subject", this, null), "subject", "subject"));
+
+        columns.add(new CollectionPropertyColumn<NotificationTaskTO>(
+                new StringResourceModel("recipients", this, null), "recipients", "recipients"));
+
+        columns.add(new DatePropertyColumn<NotificationTaskTO>(
+                new StringResourceModel("start", this, null), "start", "start"));
+
+        columns.add(new DatePropertyColumn<NotificationTaskTO>(
+                new StringResourceModel("end", this, null), "end", "end"));
+
+        columns.add(new PropertyColumn<NotificationTaskTO, String>(
+                new StringResourceModel("latestExecStatus", this, null), "latestExecStatus", "latestExecStatus"));
+
+        columns.add(new ActionColumn<NotificationTaskTO, String>(new ResourceModel("actions", "")) {
+
+            private static final long serialVersionUID = 2054811145491901166L;
+
+            @Override
+            public ActionLinksPanel<NotificationTaskTO> getActions(
+                    final String componentId, final IModel<NotificationTaskTO> model) {
+
+                final NotificationTaskTO taskTO = model.getObject();
+
+                final ActionLinksPanel<NotificationTaskTO> panel = ActionLinksPanel.<NotificationTaskTO>builder().
+                        add(new ActionLink<NotificationTaskTO>() {
+
+                            private static final long serialVersionUID = -3722207913631435501L;
+
+                            @Override
+                            public void onClick(final AjaxRequestTarget target, final NotificationTaskTO modelObject) {
+                                viewTask(taskTO, target);
+                            }
+                        }, ActionLink.ActionType.VIEW, StandardEntitlement.TASK_READ).
+                        add(new ActionLink<NotificationTaskTO>() {
+
+                            private static final long serialVersionUID = -3722207913631435501L;
+
+                            @Override
+                            public void onClick(final AjaxRequestTarget target, final NotificationTaskTO modelObject) {
+                                try {
+                                    restClient.startExecution(taskTO.getKey(), new Date());
+                                    info(getString(Constants.OPERATION_SUCCEEDED));
+                                    target.add(container);
+                                } catch (SyncopeClientException e) {
+                                    error(StringUtils.isBlank(e.getMessage())
+                                            ? e.getClass().getName() : e.getMessage());
+                                    LOG.error("While running {}", taskTO.getKey(), e);
+                                }
+                                SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
+                            }
+                        }, ActionLink.ActionType.EXECUTE, StandardEntitlement.TASK_EXECUTE).
+                        add(new ActionLink<NotificationTaskTO>() {
+
+                            private static final long serialVersionUID = -3722207913631435501L;
+
+                            @Override
+                            public void onClick(final AjaxRequestTarget target, final NotificationTaskTO modelObject) {
+                                try {
+                                    restClient.delete(taskTO.getKey(), NotificationTaskTO.class);
+                                    info(getString(Constants.OPERATION_SUCCEEDED));
+                                    target.add(container);
+                                } catch (SyncopeClientException e) {
+                                    LOG.error("While deleting {}", taskTO.getKey(), e);
+                                    error(StringUtils.isBlank(e.getMessage())
+                                            ? e.getClass().getName() : e.getMessage());
+                                }
+                                SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
+                            }
+                        }, ActionLink.ActionType.DELETE, StandardEntitlement.TASK_DELETE).build(componentId);
+
+                return panel;
+            }
+
+            @Override
+            public ActionLinksPanel<NotificationTaskTO> getHeader(final String componentId) {
+                final ActionLinksPanel.Builder<NotificationTaskTO> panel = ActionLinksPanel.builder();
+
+                return panel.add(new ActionLink<NotificationTaskTO>() {
+
+                    private static final long serialVersionUID = -7978723352517770644L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final NotificationTaskTO ignore) {
+                        if (target != null) {
+                            target.add(container);
+                        }
+                    }
+                }, ActionLink.ActionType.RELOAD, StandardEntitlement.TASK_LIST).build(componentId);
+            }
+        });
+
+        return columns;
+    }
+
+    @Override
+    protected Collection<ActionType> getBulkActions() {
+        final List<ActionType> bulkActions = new ArrayList<>();
+        bulkActions.add(ActionType.DELETE);
+        bulkActions.add(ActionType.EXECUTE);
+        return bulkActions;
+    }
+
+    @Override
+    protected NotificationTasksProvider dataProvider() {
+        return new NotificationTasksProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return Constants.PREF_PROPAGATION_TASKS_PAGINATOR_ROWS;
+    }
+
+    public class NotificationTasksProvider extends TaskDataProvider<NotificationTaskTO> {
+
+        private static final long serialVersionUID = 4725679400450513556L;
+
+        public NotificationTasksProvider(final int paginatorRows) {
+            super(paginatorRows, TaskType.PROPAGATION, restClient);
+        }
+
+        @Override
+        public Iterator<NotificationTaskTO> iterator(final long first, final long count) {
+            final int page = ((int) first / paginatorRows);
+
+            final List<NotificationTaskTO> tasks = restClient.list(
+                    NotificationTaskTO.class, (page < 0 ? 0 : page) + 1, paginatorRows, getSort());
+
+            Collections.sort(tasks, getComparator());
+            return tasks.iterator();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
index e8d902c..61d1d48 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskWizardBuilder.java
@@ -81,7 +81,7 @@ public class SchedTaskWizardBuilder<T extends SchedTaskTO> extends AjaxWizardBui
     };
 
     public SchedTaskWizardBuilder(final T taskTO, final PageReference pageRef) {
-        super("wizard", taskTO, pageRef);
+        super(taskTO, pageRef);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPanel.java
new file mode 100644
index 0000000..f04fa5a
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPanel.java
@@ -0,0 +1,47 @@
+/*
+ * 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.wicket.extensions.markup.html.repeater.data.table;
+
+import java.util.List;
+import org.apache.wicket.markup.html.basic.Label;
+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.ResourceModel;
+
+public class CollectionPanel extends Panel {
+
+    private static final long serialVersionUID = -4042497356836230377L;
+
+    @SuppressWarnings("unchecked")
+    public CollectionPanel(final String id, final List values) {
+        super(id);
+
+        add(new ListView("collection", values) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem item) {
+                final String value = item.getModelObject() == null ? null : item.getModelObject().toString();
+                item.add(new Label("item", new ResourceModel(value, value)));
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPropertyColumn.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPropertyColumn.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPropertyColumn.java
new file mode 100644
index 0000000..a5fd774
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/extensions/markup/html/repeater/data/table/CollectionPropertyColumn.java
@@ -0,0 +1,54 @@
+/*
+ * 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.wicket.extensions.markup.html.repeater.data.table;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.IModel;
+
+public class CollectionPropertyColumn<T> extends PropertyColumn<T, String> {
+
+    private static final long serialVersionUID = 8077865338230121496L;
+
+    public CollectionPropertyColumn(
+            final IModel<String> displayModel,
+            final String sortProperty,
+            final String propertyExpression) {
+        super(displayModel, sortProperty, propertyExpression);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void populateItem(
+            final Item<ICellPopulator<T>> cellItem, final String componentId, final IModel<T> rowModel) {
+
+        final Object value = getDataModel(rowModel).getObject();
+
+        if (value instanceof Collection) {
+            final List values = new ArrayList((Collection) value);
+            Collections.sort(values);
+            cellItem.add(new CollectionPanel(componentId, values));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java
new file mode 100644
index 0000000..08b304e
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AbstractMultiPanel.java
@@ -0,0 +1,217 @@
+/*
+ * 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.wicket.markup.html.form;
+
+import java.util.List;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.wicket.ajax.markup.html.IndicatorAjaxSubmitLink;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+
+public abstract class AbstractMultiPanel<INNER> extends AbstractFieldPanel<List<INNER>> {
+
+    private static final long serialVersionUID = -6322397761456513324L;
+
+    private final ListView<INNER> view;
+
+    private final boolean eventTemplate;
+
+    private final WebMarkupContainer container;
+
+    private final Form<?> form;
+
+    public AbstractMultiPanel(
+            final String id,
+            final String name,
+            final IModel<List<INNER>> model,
+            final boolean eventTemplate) {
+
+        super(id, name, model);
+
+        this.eventTemplate = eventTemplate;
+
+        // -----------------------
+        // Object container definition
+        // -----------------------
+        container = new WebMarkupContainer("multiValueContainer");
+        container.setOutputMarkupId(true);
+        add(container);
+
+        form = new Form<>("innerForm");
+        container.add(form);
+        // -----------------------
+
+        view = new InnerView("view", name, model);
+
+        final List<INNER> obj = model.getObject();
+        if (obj == null || obj.isEmpty()) {
+            form.addOrReplace(getNoDataFragment(model, name));
+        } else {
+            form.addOrReplace(getDataFragment());
+        }
+    }
+
+    private Fragment getNoDataFragment(final IModel<List<INNER>> model, final String label) {
+        final Fragment fragment = new Fragment("content", "noDataFragment", AbstractMultiPanel.this);
+        fragment.add(new Label("field-label", new ResourceModel(label, label)));
+        fragment.add(getPlusFragment(model, label));
+        return fragment;
+    }
+
+    private Fragment getDataFragment() {
+        final Fragment contentFragment = new Fragment("content", "dataFragment", AbstractMultiPanel.this);
+        contentFragment.add(view.setOutputMarkupId(true));
+        return contentFragment;
+    }
+
+    private Fragment getPlusFragment(final IModel<List<INNER>> model, final String label) {
+        final IndicatorAjaxSubmitLink plus = new IndicatorAjaxSubmitLink("add") {
+
+            private static final long serialVersionUID = -7978723352517770644L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                //Add current component
+                model.getObject().add(newModelObject());
+
+                if (model.getObject().size() == 1) {
+                    form.addOrReplace(getDataFragment());
+                }
+
+                target.add(container);
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+                error(getString(Constants.OPERATION_ERROR));
+                super.onError(target, form);
+                SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
+            }
+
+        };
+
+        final Fragment fragment = new Fragment("panelPlus", "fragmentPlus", AbstractMultiPanel.this);
+        fragment.addOrReplace(plus);
+        fragment.setRenderBodyOnly(true);
+
+        return fragment;
+    }
+
+    public ListView<INNER> getView() {
+        return view;
+    }
+
+    @Override
+    public AbstractMultiPanel<INNER> setModelObject(final List<INNER> object) {
+        view.setModelObject(object);
+        return this;
+    }
+
+    public static class MultiValueSelectorEvent {
+
+        private final AjaxRequestTarget target;
+
+        public MultiValueSelectorEvent(final AjaxRequestTarget target) {
+            this.target = target;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+    }
+
+    protected abstract INNER newModelObject();
+
+    private final class InnerView extends ListView<INNER> {
+
+        private static final long serialVersionUID = -9180479401817023838L;
+
+        private final String label;
+
+        private final IModel<List<INNER>> model;
+
+        private InnerView(final String id, final String label, final IModel<List<INNER>> model) {
+            super(id, model);
+            this.label = label;
+            this.model = model;
+        }
+
+        @Override
+        protected void populateItem(final ListItem<INNER> item) {
+
+            final Panel panel = getItemPanel(item);
+
+            item.add(panel.setRenderBodyOnly(true));
+
+            final IndicatorAjaxSubmitLink minus = new IndicatorAjaxSubmitLink("drop") {
+
+                private static final long serialVersionUID = -7978723352517770644L;
+
+                @Override
+                protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                    //Drop current component
+                    model.getObject().remove(item.getModelObject());
+                    clearInput(panel);
+
+                    if (model.getObject().isEmpty()) {
+                        form.addOrReplace(getNoDataFragment(model, label));
+                    }
+
+                    target.add(container);
+
+                    if (eventTemplate) {
+                        send(getPage(), Broadcast.BREADTH, new MultiValueSelectorEvent(target));
+                    }
+                }
+
+                @Override
+                protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+                    onSubmit(target, form);
+                }
+            };
+
+            item.add(minus);
+
+            final Fragment fragment;
+            if (item.getIndex() == model.getObject().size() - 1) {
+                fragment = getPlusFragment(model, label);
+            } else {
+                fragment = new Fragment("panelPlus", "emptyFragment", AbstractMultiPanel.this);
+            }
+
+            item.add(fragment.setRenderBodyOnly(true));
+        }
+    }
+
+    protected abstract Panel getItemPanel(final ListItem<INNER> item);
+
+    protected void clearInput(final Panel panel) {
+        // do nothing by default
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
index 8243d22..c254074 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
@@ -46,6 +46,8 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
         CLONE("create"),
         CREATE("create"),
         EDIT("read"),
+        HTML_EDIT("read"),
+        TEXT_EDIT("read"),
         RESET("update"),
         ENABLE("update"),
         NOT_FOND("read"),

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
index 5474291..44b070a 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLinksPanel.java
@@ -61,6 +61,8 @@ public final class ActionLinksPanel<T extends Serializable> extends Panel {
         super.add(new Fragment("panelClone", "emptyFragment", this));
         super.add(new Fragment("panelCreate", "emptyFragment", this));
         super.add(new Fragment("panelEdit", "emptyFragment", this));
+        super.add(new Fragment("panelHtmlEdit", "emptyFragment", this));
+        super.add(new Fragment("panelTextEdit", "emptyFragment", this));
         super.add(new Fragment("panelReset", "emptyFragment", this));
         super.add(new Fragment("panelEnable", "emptyFragment", this));
         super.add(new Fragment("panelNotFound", "emptyFragment", this));
@@ -306,6 +308,44 @@ public final class ActionLinksPanel<T extends Serializable> extends Panel {
                 }.setVisible(link.isEnabled(model.getObject())));
                 break;
 
+            case HTML_EDIT:
+                fragment = new Fragment("panelHtmlEdit", "fragmentHtmlEdit", this);
+
+                fragment.addOrReplace(new IndicatingAjaxLink<Void>("htmlEditLink") {
+
+                    private static final long serialVersionUID = -7978723352517770644L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        link.onClick(target, model.getObject());
+                    }
+
+                    @Override
+                    public String getAjaxIndicatorMarkupId() {
+                        return disableIndicator ? StringUtils.EMPTY : super.getAjaxIndicatorMarkupId();
+                    }
+                }.setVisible(link.isEnabled(model.getObject())));
+                break;
+
+            case TEXT_EDIT:
+                fragment = new Fragment("panelTextEdit", "fragmentTextEdit", this);
+
+                fragment.addOrReplace(new IndicatingAjaxLink<Void>("textEditLink") {
+
+                    private static final long serialVersionUID = -7978723352517770644L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        link.onClick(target, model.getObject());
+                    }
+
+                    @Override
+                    public String getAjaxIndicatorMarkupId() {
+                        return disableIndicator ? StringUtils.EMPTY : super.getAjaxIndicatorMarkupId();
+                    }
+                }.setVisible(link.isEnabled(model.getObject())));
+                break;
+
             case ENABLE:
                 fragment = new Fragment("panelEnable", "fragmentEnable", this);
 
@@ -788,6 +828,14 @@ public final class ActionLinksPanel<T extends Serializable> extends Panel {
                 super.addOrReplace(new Fragment("panelEdit", "emptyFragment", this));
                 break;
 
+            case HTML_EDIT:
+                super.addOrReplace(new Fragment("panelHtmlEdit", "emptyFragment", this));
+                break;
+
+            case TEXT_EDIT:
+                super.addOrReplace(new Fragment("panelTestEdit", "emptyFragment", this));
+                break;
+
             case VIEW:
                 super.addOrReplace(new Fragment("panelView", "emptyFragment", this));
                 break;


Mime
View raw message