syncope-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From matte...@apache.org
Subject syncope git commit: SYNCOPE-872 shows type extensions on user form
Date Wed, 03 Aug 2016 16:20:30 GMT
Repository: syncope
Updated Branches:
  refs/heads/master d0d69fea0 -> 343b3d400


SYNCOPE-872 shows type extensions on user form


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

Branch: refs/heads/master
Commit: 343b3d400fd2457e2458c52eacd03af17c51dbac
Parents: d0d69fe
Author: Matteo Di Carlo <matteo.dicarlo@tirasa.net>
Authored: Wed Aug 3 18:19:38 2016 +0200
Committer: Matteo Di Carlo <matteo.dicarlo@tirasa.net>
Committed: Wed Aug 3 18:19:38 2016 +0200

----------------------------------------------------------------------
 .../client/enduser/model/SchemaResponse.java    | 28 +++----
 .../enduser/resources/SchemaResource.java       | 78 +++++++++++++-----
 .../resources/UserSelfCreateResource.java       | 84 +++++++++++++++++++-
 .../enduser/resources/UserSelfReadResource.java | 20 +++++
 .../resources/UserSelfUpdateResource.java       | 84 +++++++++++++++++++-
 .../app/js/controllers/UserController.js        | 50 +++++++++---
 .../js/directives/dynamicDerivedAttributes.js   | 38 ++++++++-
 .../app/js/directives/dynamicPlainAttribute.js  |  6 +-
 .../app/js/directives/dynamicPlainAttributes.js | 72 +++++++++++------
 .../js/directives/dynamicVirtualAttribute.js    |  2 +-
 .../js/directives/dynamicVirtualAttributes.js   | 67 ++++++++++------
 .../resources/app/js/directives/groups.js       | 18 +++--
 .../app/js/directives/validationMessage.js      |  2 +-
 .../resources/app/js/services/schemaService.js  | 16 +++-
 .../resources/app/languages/de/static.json      |  4 +-
 .../resources/app/languages/en/static.json      |  4 +-
 .../resources/app/languages/it/static.json      |  3 +-
 .../app/views/dynamicDerivedAttributes.html     | 38 +++++++--
 .../app/views/dynamicPlainAttributes.html       | 50 +++++++-----
 .../app/views/dynamicVirtualAttributes.html     | 45 +++++++----
 .../common/rest/api/service/GroupService.java   | 15 ++++
 .../apache/syncope/core/logic/GroupLogic.java   | 13 +++
 .../java/data/AnyObjectDataBinderImpl.java      |  7 +-
 .../java/data/UserDataBinderImpl.java           |  7 +-
 .../core/rest/cxf/service/GroupServiceImpl.java |  6 ++
 25 files changed, 589 insertions(+), 168 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java
index 912f287..38efba2 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/SchemaResponse.java
@@ -21,58 +21,56 @@ package org.apache.syncope.client.enduser.model;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.syncope.common.lib.to.DerSchemaTO;
-import org.apache.syncope.common.lib.to.PlainSchemaTO;
-import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.to.AbstractSchemaTO;
 
 public class SchemaResponse implements Serializable {
 
     private static final long serialVersionUID = -8896862106241712829L;
 
-    private List<PlainSchemaTO> plainSchemas = new ArrayList<>();
+    private List<AbstractSchemaTO> plainSchemas = new ArrayList<>();
 
-    private List<DerSchemaTO> derSchemas = new ArrayList<>();
+    private List<AbstractSchemaTO> derSchemas = new ArrayList<>();
 
-    private List<VirSchemaTO> virSchemas = new ArrayList<>();
+    private List<AbstractSchemaTO> virSchemas = new ArrayList<>();
 
     public SchemaResponse() {
     }
 
-    public List<PlainSchemaTO> getPlainSchemas() {
+    public List<AbstractSchemaTO> getPlainSchemas() {
         return plainSchemas;
     }
 
-    public void setPlainSchemas(final List<PlainSchemaTO> plainSchemas) {
+    public void setPlainSchemas(final List<AbstractSchemaTO> plainSchemas) {
         this.plainSchemas = plainSchemas;
     }
 
-    public List<DerSchemaTO> getDerSchemas() {
+    public List<AbstractSchemaTO> getDerSchemas() {
         return derSchemas;
     }
 
-    public void setDerSchemas(final List<DerSchemaTO> derSchemas) {
+    public void setDerSchemas(final List<AbstractSchemaTO> derSchemas) {
         this.derSchemas = derSchemas;
     }
 
-    public List<VirSchemaTO> getVirSchemas() {
+    public List<AbstractSchemaTO> getVirSchemas() {
         return virSchemas;
     }
 
-    public void setVirSchemas(final List<VirSchemaTO> virSchemas) {
+    public void setVirSchemas(final List<AbstractSchemaTO> virSchemas) {
         this.virSchemas = virSchemas;
     }
 
-    public SchemaResponse plainSchemas(final List<PlainSchemaTO> value) {
+    public SchemaResponse plainSchemas(final List<AbstractSchemaTO> value) {
         this.plainSchemas = value;
         return this;
     }
 
-    public SchemaResponse derSchemas(final List<DerSchemaTO> value) {
+    public SchemaResponse derSchemas(final List<AbstractSchemaTO> value) {
         this.derSchemas = value;
         return this;
     }
 
-    public SchemaResponse virSchemas(final List<VirSchemaTO> value) {
+    public SchemaResponse virSchemas(final List<AbstractSchemaTO> value) {
         this.virSchemas = value;
         return this;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
index a46e953..605426a 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java
@@ -19,20 +19,24 @@
 package org.apache.syncope.client.enduser.resources;
 
 import java.io.IOException;
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.Response;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.model.SchemaResponse;
+import org.apache.syncope.common.lib.to.AbstractSchemaTO;
 import org.apache.syncope.common.lib.to.AnyTypeTO;
-import org.apache.syncope.common.lib.to.DerSchemaTO;
-import org.apache.syncope.common.lib.to.PlainSchemaTO;
-import org.apache.syncope.common.lib.to.VirSchemaTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.PagedResult;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.beans.SchemaQuery;
 import org.apache.syncope.common.rest.api.service.AnyTypeService;
+import org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.common.rest.api.service.SchemaService;
 import org.apache.wicket.request.resource.AbstractResource;
 import org.apache.wicket.request.resource.IResource;
@@ -45,9 +49,12 @@ public class SchemaResource extends AbstractBaseResource {
 
     private final SchemaService schemaService;
 
+    private final GroupService groupService;
+
     public SchemaResource() {
         anyTypeService = SyncopeEnduserSession.get().getService(AnyTypeService.class);
         schemaService = SyncopeEnduserSession.get().getService(SchemaService.class);
+        groupService = SyncopeEnduserSession.get().getService(GroupService.class);
     }
 
     @Override
@@ -66,24 +73,57 @@ public class SchemaResource extends AbstractBaseResource {
                 return response;
             }
 
-            final AnyTypeTO anyTypeUserTO = anyTypeService.read(AnyTypeKind.USER.name());
+            List<String> classes = Collections.emptyList();
+
+            final String groupParam = attributes.getParameters().get("group").toString();
+            if (groupParam != null) {
+                PagedResult<GroupTO> groups = groupService.search(
+                        new AnyQuery.Builder().realm("/").page(1).size(1000).build());
+                GroupTO group = IterableUtils.find(groups.getResult(), new Predicate<GroupTO>() {
+
+                    @Override
+                    public boolean evaluate(final GroupTO item) {
+                        return groupParam.equals(item.getName());
+                    }
+                });
 
-            List<String> classes = new ArrayList<>();
-            String parameter = attributes.getParameters().get("anyTypeClass").toString();
-            if (parameter != null) {
-                classes.add(parameter);
+                if (group != null && group.getTypeExtension(AnyTypeKind.USER.name()) != null) {
+                    classes = group.getTypeExtension(AnyTypeKind.USER.name()).getAuxClasses();
+                }
             } else {
-                classes = anyTypeUserTO.getClasses();
+                String anyTypeClass = attributes.getParameters().get("anyTypeClass").toString();
+                if (anyTypeClass != null) {
+                    classes = Collections.singletonList(anyTypeClass);
+                } else {
+                    AnyTypeTO anyTypeUserTO = anyTypeService.read(AnyTypeKind.USER.name());
+                    classes = anyTypeUserTO.getClasses();
+                }
+            }
+
+            final List<AbstractSchemaTO> plainSchemas = classes.isEmpty()
+                    ? Collections.<AbstractSchemaTO>emptyList()
+                    : schemaService.list(
+                            new SchemaQuery.Builder().type(SchemaType.PLAIN).anyTypeClasses(classes).build());
+            final List<AbstractSchemaTO> derSchemas = classes.isEmpty()
+                    ? Collections.<AbstractSchemaTO>emptyList()
+                    : schemaService.list(
+                            new SchemaQuery.Builder().type(SchemaType.DERIVED).anyTypeClasses(classes).build());
+            final List<AbstractSchemaTO> virSchemas = classes.isEmpty()
+                    ? Collections.<AbstractSchemaTO>emptyList()
+                    : schemaService.list(
+                            new SchemaQuery.Builder().type(SchemaType.VIRTUAL).anyTypeClasses(classes).build());
+
+            if (groupParam != null) {
+                for (AbstractSchemaTO schema : plainSchemas) {
+                    schema.setKey(groupParam + "#" + schema.getKey());
+                }
+                for (AbstractSchemaTO schema : derSchemas) {
+                    schema.setKey(groupParam + "#" + schema.getKey());
+                }
+                for (AbstractSchemaTO schema : virSchemas) {
+                    schema.setKey(groupParam + "#" + schema.getKey());
+                }
             }
-            final List<PlainSchemaTO> plainSchemas = schemaService.list(
-                    new SchemaQuery.Builder().type(SchemaType.PLAIN).
-                    anyTypeClasses(classes).build());
-            final List<DerSchemaTO> derSchemas = schemaService.list(
-                    new SchemaQuery.Builder().type(SchemaType.DERIVED).
-                    anyTypeClasses(classes).build());
-            final List<VirSchemaTO> virSchemas = schemaService.list(
-                    new SchemaQuery.Builder().type(SchemaType.VIRTUAL).
-                    anyTypeClasses(classes).build());
 
             response.setWriteCallback(new AbstractResource.WriteCallback() {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
index 3453884..58d108d 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java
@@ -19,10 +19,17 @@
 package org.apache.syncope.client.enduser.resources;
 
 import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.Response;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserConstants;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.slf4j.Logger;
@@ -55,7 +62,6 @@ public class UserSelfCreateResource extends AbstractBaseResource {
             }
 
             String jsonString = request.getReader().readLine();
-
             final UserTO userTO = MAPPER.readValue(jsonString, UserTO.class);
 
             if (!captchaCheck(
@@ -66,6 +72,81 @@ public class UserSelfCreateResource extends AbstractBaseResource {
             }
 
             if (isSelfRegistrationAllowed() && userTO != null) {
+                Set<AttrTO> membAttrs = new HashSet<>();
+                for (AttrTO attr : userTO.getPlainAttrs()) {
+                    if (attr.getSchema().contains("#")) {
+                        final String[] simpleAttrs = attr.getSchema().split("#");
+                        MembershipTO membership = IterableUtils.find(userTO.getMemberships(),
+                                new Predicate<MembershipTO>() {
+
+                            @Override
+                            public boolean evaluate(final MembershipTO item) {
+                                return simpleAttrs[0].equals(item.getGroupName());
+                            }
+                        });
+                        if (membership == null) {
+                            membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build();
+                            userTO.getMemberships().add(membership);
+                        }
+
+                        AttrTO clone = SerializationUtils.clone(attr);
+                        clone.setSchema(simpleAttrs[1]);
+                        membership.getPlainAttrs().add(clone);
+                        membAttrs.add(attr);
+                    }
+                }
+                userTO.getPlainAttrs().removeAll(membAttrs);
+
+                membAttrs.clear();
+                for (AttrTO attr : userTO.getDerAttrs()) {
+                    if (attr.getSchema().contains("#")) {
+                        final String[] simpleAttrs = attr.getSchema().split("#");
+                        MembershipTO membership = IterableUtils.find(userTO.getMemberships(),
+                                new Predicate<MembershipTO>() {
+
+                            @Override
+                            public boolean evaluate(final MembershipTO item) {
+                                return simpleAttrs[0].equals(item.getGroupName());
+                            }
+                        });
+                        if (membership == null) {
+                            membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build();
+                            userTO.getMemberships().add(membership);
+                        }
+
+                        AttrTO clone = SerializationUtils.clone(attr);
+                        clone.setSchema(simpleAttrs[1]);
+                        membership.getDerAttrs().add(clone);
+                        membAttrs.add(attr);
+                    }
+                }
+                userTO.getDerAttrs().removeAll(membAttrs);
+
+                membAttrs.clear();
+                for (AttrTO attr : userTO.getVirAttrs()) {
+                    if (attr.getSchema().contains("#")) {
+                        final String[] simpleAttrs = attr.getSchema().split("#");
+                        MembershipTO membership = IterableUtils.find(userTO.getMemberships(),
+                                new Predicate<MembershipTO>() {
+
+                            @Override
+                            public boolean evaluate(final MembershipTO item) {
+                                return simpleAttrs[0].equals(item.getGroupName());
+                            }
+                        });
+                        if (membership == null) {
+                            membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build();
+                            userTO.getMemberships().add(membership);
+                        }
+
+                        AttrTO clone = SerializationUtils.clone(attr);
+                        clone.setSchema(simpleAttrs[1]);
+                        membership.getVirAttrs().add(clone);
+                        membAttrs.add(attr);
+                    }
+                }
+                userTO.getVirAttrs().removeAll(membAttrs);
+
                 LOG.debug("Received user self registration request for user: [{}]", userTO.getUsername());
                 LOG.trace("Received user self registration request is: [{}]", userTO);
                 // adapt request and create user
@@ -102,5 +183,4 @@ public class UserSelfCreateResource extends AbstractBaseResource {
         }
         return response;
     }
-
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java
index b609118..4f04f31 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java
@@ -28,6 +28,7 @@ import org.apache.commons.lang3.SerializationUtils;
 import org.apache.commons.lang3.time.FastDateFormat;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.wicket.request.resource.AbstractResource;
@@ -69,6 +70,25 @@ public class UserSelfReadResource extends AbstractBaseResource {
                 }
             }
 
+            for (MembershipTO membership : userTO.getMemberships()) {
+                String groupName = membership.getGroupName();
+                for (AttrTO attr : membership.getPlainAttrs()) {
+                    attr.setSchema(groupName.concat("#").concat(attr.getSchema()));
+                    userTO.getPlainAttrs().add(attr);
+                }
+                membership.getPlainAttrs().clear();
+                for (AttrTO attr : membership.getDerAttrs()) {
+                    attr.setSchema(groupName.concat("#").concat(attr.getSchema()));
+                    userTO.getDerAttrs().add(attr);
+                }
+                membership.getDerAttrs().clear();
+                for (AttrTO attr : membership.getVirAttrs()) {
+                    attr.setSchema(groupName.concat("#").concat(attr.getSchema()));
+                    userTO.getVirAttrs().add(attr);
+                }
+                membership.getVirAttrs().clear();
+            }
+
             final String selfTOJson = MAPPER.writeValueAsString(userTO);
             response.setWriteCallback(new WriteCallback() {
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
index 119b697..561811a 100644
--- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
+++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java
@@ -20,14 +20,20 @@ package org.apache.syncope.client.enduser.resources;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.core.Response;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang3.SerializationUtils;
 import org.apache.commons.lang3.time.FastDateFormat;
 import org.apache.syncope.client.enduser.SyncopeEnduserConstants;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
@@ -58,8 +64,7 @@ public class UserSelfUpdateResource extends AbstractBaseResource {
             }
 
             UserTO userTO = MAPPER.readValue(request.getReader().readLine(), UserTO.class);
-            LOG.debug("User {} id self-updating", userTO.getUsername());
-
+            
             Map<String, AttrTO> userPlainAttrMap = userTO.getPlainAttrMap();
 
             for (PlainSchemaTO plainSchema : SyncopeEnduserSession.get().getDatePlainSchemas()) {
@@ -80,6 +85,79 @@ public class UserSelfUpdateResource extends AbstractBaseResource {
                 }
             }
 
+            Set<AttrTO> membAttrs = new HashSet<>();
+            for (AttrTO attr : userTO.getPlainAttrs()) {
+                if (attr.getSchema().contains("#")) {
+                    final String[] simpleAttrs = attr.getSchema().split("#");
+                    MembershipTO membership = IterableUtils.find(userTO.getMemberships(),
+                            new Predicate<MembershipTO>() {
+
+                        @Override
+                        public boolean evaluate(final MembershipTO item) {
+                            return simpleAttrs[0].equals(item.getGroupName());
+                        }
+                    });
+                    if (membership == null) {
+                        membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build();
+                        userTO.getMemberships().add(membership);
+                    }
+                    AttrTO clone = SerializationUtils.clone(attr);
+                    clone.setSchema(simpleAttrs[1]);
+                    membership.getPlainAttrs().add(clone);
+                    membAttrs.add(attr);
+                }
+            }
+            userTO.getPlainAttrs().removeAll(membAttrs);
+
+            membAttrs.clear();
+            for (AttrTO attr : userTO.getDerAttrs()) {
+                if (attr.getSchema().contains("#")) {
+                    final String[] simpleAttrs = attr.getSchema().split("#");
+                    MembershipTO membership = IterableUtils.find(userTO.getMemberships(),
+                            new Predicate<MembershipTO>() {
+
+                        @Override
+                        public boolean evaluate(final MembershipTO item) {
+                            return simpleAttrs[0].equals(item.getGroupName());
+                        }
+                    });
+                    if (membership == null) {
+                        membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build();
+                        userTO.getMemberships().add(membership);
+                    }
+                    AttrTO clone = SerializationUtils.clone(attr);
+                    clone.setSchema(simpleAttrs[1]);
+                    membership.getDerAttrs().add(clone);
+                    membAttrs.add(attr);
+                }
+            }
+            userTO.getDerAttrs().removeAll(membAttrs);
+
+            membAttrs.clear();
+            for (AttrTO attr : userTO.getVirAttrs()) {
+                if (attr.getSchema().contains("#")) {
+                    final String[] simpleAttrs = attr.getSchema().split("#");
+                    MembershipTO membership = IterableUtils.find(userTO.getMemberships(),
+                            new Predicate<MembershipTO>() {
+
+                        @Override
+                        public boolean evaluate(final MembershipTO item) {
+                            return simpleAttrs[0].equals(item.getGroupName());
+                        }
+                    });
+                    if (membership == null) {
+                        membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build();
+                        userTO.getMemberships().add(membership);
+
+                    }
+                    AttrTO clone = SerializationUtils.clone(attr);
+                    clone.setSchema(simpleAttrs[1]);
+                    membership.getVirAttrs().add(clone);
+                    membAttrs.add(attr);
+                }
+            }
+            userTO.getVirAttrs().removeAll(membAttrs);
+
             // update user
             Response res = SyncopeEnduserSession.get().
                     getService(userTO.getETagValue(), UserSelfService.class).update(userTO);
@@ -89,7 +167,6 @@ public class UserSelfUpdateResource extends AbstractBaseResource {
                     append("User").append(userTO.getUsername()).append(" successfully updated").toString()
                     : new StringBuilder().
                     append("ErrorMessage{{ ").append(res.getStatusInfo().getReasonPhrase()).append(" }}").toString();
-
             response.setWriteCallback(new WriteCallback() {
 
                 @Override
@@ -111,5 +188,4 @@ public class UserSelfUpdateResource extends AbstractBaseResource {
         }
         return response;
     }
-
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
index a4a3135..f0e2ec5 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js
@@ -56,12 +56,21 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         virtualAttributeTable: {},
         selectedResources: [],
         selectedGroups: [],
-        selectedAuxClasses: []
+        selectedAuxClasses: [],
+        groupSchemas: ['own']
       };
 
-      var initUserSchemas = function (anyTypeClass) {
+      var initUserSchemas = function (anyTypeClass, group) {
         // initialization is done here synchronously to have all schema fields populated correctly
-        SchemaService.getUserSchemas(anyTypeClass).then(function (schemas) {
+        var schemaService;
+        if (group) {
+          schemaService = SchemaService.getTypeExtSchemas(group);
+        } else {
+          schemaService = SchemaService.getUserSchemas(anyTypeClass);
+        }
+        schemaService.then(function (schemas) {
+          if(group && ( schemas.plainSchemas.length > 0 || schemas.derSchemas.length > 0 || schemas.virSchemas.length > 0))
+            $scope.dynamicForm.groupSchemas.push(group);
           //initializing user schemas values
           initSchemaValues(schemas);
         }, function (response) {
@@ -188,7 +197,6 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
       };
 
       var initAuxClasses = function () {
-
         //fetching default user classes, that should remain in any case
         AnyService.getAnyType("USER").then(function (response) {
           $scope.dynamicForm.anyUserType = response.classes;
@@ -242,6 +250,9 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
           for (var index in $scope.user.auxClasses) {
             $scope.$emit("auxClassAdded", $scope.user.auxClasses[index]);
           }
+          for (var index in $scope.user.memberships) {
+            $scope.$emit("groupAdded", $scope.user.memberships[index].groupName);
+          }
           if ($scope.user.mustChangePassword) {
             $location.path('/mustchangepassword')
           } else {
@@ -252,11 +263,12 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         });
       };
 
-      var removeUserSchemas = function (anyTypeClass) {
-
+      var removeUserSchemas = function (anyTypeClass, group) {
         //removing plain schemas
         for (var i = 0; i < $scope.dynamicForm.plainSchemas.length; i++) {
-          if ($scope.dynamicForm.plainSchemas[i].anyTypeClass == anyTypeClass) {
+          if ((anyTypeClass && $scope.dynamicForm.plainSchemas[i].anyTypeClass == anyTypeClass)
+                  || (group && $scope.dynamicForm.plainSchemas[i].key.includes(group + '#'))) {
+
             //cleaning both form and user model
             delete $scope.user.plainAttrs[$scope.dynamicForm.plainSchemas[i].key];
             $scope.dynamicForm.plainSchemas.splice(i, 1);
@@ -265,7 +277,9 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         }
         //removing derived schemas
         for (var i = 0; i < $scope.dynamicForm.derSchemas.length; i++) {
-          if ($scope.dynamicForm.derSchemas[i].anyTypeClass == anyTypeClass) {
+          if ((anyTypeClass && $scope.dynamicForm.derSchemas[i].anyTypeClass == anyTypeClass)
+                  || (group && $scope.dynamicForm.derSchemas[i].key.includes(group + '#'))) {
+
             //cleaning both form and user model
             delete $scope.user.derAttrs[$scope.dynamicForm.derSchemas[i].key];
             $scope.dynamicForm.derSchemas.splice(i, 1);
@@ -274,7 +288,9 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         }
         //removing virtual schemas
         for (var i = 0; i < $scope.dynamicForm.virSchemas.length; i++) {
-          if ($scope.dynamicForm.virSchemas[i].anyTypeClass == anyTypeClass) {
+          if ((anyTypeClass && $scope.dynamicForm.virSchemas[i].anyTypeClass == anyTypeClass)
+                  || (group && $scope.dynamicForm.virSchemas[i].key.includes(group + '#'))) {
+
             //cleaning both form and user model
             delete $scope.user.virAttrs[$scope.dynamicForm.virSchemas[i].key];
             $scope.dynamicForm.virSchemas.splice(i, 1);
@@ -294,8 +310,17 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
           removeUserSchemas(auxClass);
       });
 
-      if ($scope.createMode) {
+      $scope.$on('groupAdded', function (event, group) {
+        if (group)
+          initUserSchemas(null, group);
+      });
+
+      $scope.$on('groupRemoved', function (event, group) {
+        if (group)
+          removeUserSchemas(null, group);
+      });
 
+      if ($scope.createMode) {
         $scope.user = {
           username: '',
           password: '',
@@ -314,6 +339,10 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
         for (var index in $scope.dynamicForm.selectedAuxClasses) {
           initUserSchemas($scope.dynamicForm.selectedAuxClasses[index]);
         }
+        // initialize groups in case of pre-existing groups
+        for (var index in $scope.dynamicForm.selectedGroups) {
+          initUserSchemas(null, $scope.dynamicForm.selectedGroups[index]);
+        }
         initProperties();
       } else {
         // read user from syncope core
@@ -324,7 +353,6 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l
     $scope.saveUser = function (user) {
       console.debug("Save user: ", user);
       var wrappedUser = UserUtil.getWrappedUser(user);
-
       if ($scope.createMode) {
         UserSelfService.create(wrappedUser, $scope.captchaInput.value).then(function (response) {
           console.info("User " + $scope.user.username + " SUCCESSFULLY_CREATED");

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js
index 6e4e3aa..7898767 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicDerivedAttributes.js
@@ -16,16 +16,52 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 'use strict';
 
 angular.module('self')
         .directive('dynamicDerivedAttributes', function () {
+          var getTemplateUrl = function () {
+            return 'views/dynamicDerivedAttributes.html';
+          };
           return {
             restrict: 'E',
-            templateUrl: 'views/dynamicDerivedAttributes.html',
+            templateUrl: getTemplateUrl(),
             scope: {
               dynamicForm: "=form",
               user: "="
+            },
+            controller: function ($scope) {
+              $scope.getByGroup = function (schema) {
+                var currentDerivedSchemas = new Array();
+                for (var i = 0; i < $scope.dynamicForm.derSchemas.length; i++) {
+                  if (schema == "own" && $scope.dynamicForm.derSchemas[i].key.indexOf('#') == -1) {
+                    var attr = $scope.dynamicForm.derSchemas[i];
+                    attr.simpleKey = $scope.dynamicForm.derSchemas[i].key;
+                    currentDerivedSchemas.push($scope.dynamicForm.derSchemas[i]);
+                  }
+                  if ($scope.dynamicForm.derSchemas[i].key.indexOf(schema) > -1) {
+                    var attr = $scope.dynamicForm.derSchemas[i];
+                    attr.simpleKey = $scope.dynamicForm.derSchemas[i].key.substring($scope.dynamicForm.derSchemas[i].key.indexOf("#") + 1);
+                    currentDerivedSchemas.push(attr);
+                  }
+                }
+                return currentDerivedSchemas;
+              };
+
+              $scope.addAttributeField = function (derSchemaKey) {
+                console.debug("Add DERIVED value:", derSchemaKey);
+                console.debug(" ", ($scope.dynamicForm.attributeTable[derSchemaKey].fields.length));
+                $scope.dynamicForm.attributeTable[derSchemaKey].fields.push(derSchemaKey + "_" + ($scope.dynamicForm.attributeTable[derSchemaKey].fields.length));
+              };
+
+              $scope.removeAttributeField = function (derSchemaKey, index) {
+                console.debug("Remove DERIVED value:", derSchemaKey);
+                console.debug("attribute index:", index);
+                $scope.dynamicForm.attributeTable[derSchemaKey].fields.splice(index, 1);
+                // clean user model
+                $scope.user.derAttrs[derSchemaKey].values.splice(index, 1);
+              };
             }
           };
         });

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
index 8bf7324..0b9526c 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttribute.js
@@ -31,7 +31,6 @@ angular.module('self')
             },
             controller: function ($scope, $element, $window) {
               $scope.initAttribute = function (schema, index) {
-
                 switch (schema.type) {
                   case "Long":
                   case "Double":
@@ -52,7 +51,6 @@ angular.module('self')
                             || $scope.enumerationValues[0];
                     break;
                   case "Binary":
-
                     $scope.userFile = $scope.userFile || '';
                     $element.bind("change", function (changeEvent) {
                       $scope.$apply(function () {
@@ -89,10 +87,8 @@ angular.module('self')
 
                   case "Date":
                     var dateInMs = $scope.user.plainAttrs[schema.key].values[index];
-                    console.debug("received date in milliseconds", dateInMs);
                     if (dateInMs) {
                       var temporaryDate = new Date(dateInMs * 1);
-                      console.debug("date-time", temporaryDate);
                       $scope.selectedDate = temporaryDate;
                       $scope.selectedTime = temporaryDate;
                     }
@@ -207,6 +203,6 @@ angular.module('self')
                           return (n !== undefined && n !== "");
                         });
               });
-            },
+            }
           };
         });

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js
index 65d6e2c..a4014e9 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicPlainAttributes.js
@@ -19,29 +19,53 @@
 'use strict';
 
 angular.module('self')
-        .directive('dynamicPlainAttributes', function (SchemaService) {
-          return {
-            restrict: 'E',
-            templateUrl: 'views/dynamicPlainAttributes.html',
-            scope: {
-              dynamicForm: "=form",
-              user: "="
-            },
-            controller: function ($scope) {
+        .directive('dynamicPlainAttributes', function () {
+            var getTemplateUrl = function () {
+              return 'views/dynamicPlainAttributes.html';
+            };
+            return {
+              restrict: 'E',
+              templateUrl: getTemplateUrl(),
+              scope: {
+                dynamicForm: "=form",
+                user: "="
+              },
+              controller: function ($scope) {
+                
+                $scope.getByGroup = function (schema) {
+                  var currentPlainSchemas = new Array();
+                  for (var i = 0; i < $scope.dynamicForm.plainSchemas.length; i++) {
+                    if (schema == "own" && $scope.dynamicForm.plainSchemas[i].key.indexOf('#') == -1) {
+                      var attr = $scope.dynamicForm.plainSchemas[i];
+                      attr.simpleKey = $scope.dynamicForm.plainSchemas[i].key;
+                      currentPlainSchemas.push($scope.dynamicForm.plainSchemas[i]);
+                    }
+                    if ($scope.dynamicForm.plainSchemas[i].key.indexOf(schema) > -1) {
+                      var attr = $scope.dynamicForm.plainSchemas[i];
+                      attr.simpleKey = $scope.dynamicForm.plainSchemas[i].key.substring($scope.dynamicForm.plainSchemas[i].key.indexOf("#") + 1);
+                      currentPlainSchemas.push(attr);
+                    }
+                  }
+                  return currentPlainSchemas;
+                };
 
-              $scope.addAttributeField = function (plainSchemaKey) {
-                console.debug("Add PLAIN value:", plainSchemaKey);
-                console.debug(" ",($scope.dynamicForm.attributeTable[plainSchemaKey].fields.length));
-                $scope.dynamicForm.attributeTable[plainSchemaKey].fields.push(plainSchemaKey + "_" + ($scope.dynamicForm.attributeTable[plainSchemaKey].fields.length));
-              };
+                $scope.addAttributeField = function (plainSchemaKey) {
+                  console.debug("Add PLAIN value:", plainSchemaKey);
+                  console.debug(" ", ($scope.dynamicForm.attributeTable[plainSchemaKey].fields.length));
+                  $scope.dynamicForm.attributeTable[plainSchemaKey].fields.push(plainSchemaKey + "_" + ($scope.dynamicForm.attributeTable[plainSchemaKey].fields.length));
+                };
 
-              $scope.removeAttributeField = function (plainSchemaKey, index) {
-                console.debug("Remove PLAIN value:", plainSchemaKey);
-                console.debug("attribute index:", index);
-                $scope.dynamicForm.attributeTable[plainSchemaKey].fields.splice(index, 1);
-                // clean user model
-                $scope.user.plainAttrs[plainSchemaKey].values.splice(index, 1);
-              };
-            }
-          };
-        });
+                $scope.removeAttributeField = function (plainSchemaKey, index) {
+                  console.debug("Remove PLAIN value:", plainSchemaKey);
+                  console.debug("attribute index:", index);
+                  $scope.dynamicForm.attributeTable[plainSchemaKey].fields.splice(index, 1);
+                  // clean user model
+                  $scope.user.plainAttrs[plainSchemaKey].values.splice(index, 1);
+                };
+
+                $scope.getTemplateUrl = function () {
+                  return "views/dynamicPlainAttributes.html";
+                };
+              }
+            };
+          });

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
index 7217e0a..6fc926b 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttribute.js
@@ -37,7 +37,7 @@ angular.module('self')
                           return (n !== undefined && n !== "");
                         });
               });
-            },
+            }
             //replace: true
           };
         });

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
index 4f9b92b..6b08320 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/dynamicVirtualAttributes.js
@@ -16,32 +16,53 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 'use strict';
 
 angular.module('self')
         .directive('dynamicVirtualAttributes', function () {
-          return {
-            restrict: 'E',
-            templateUrl: 'views/dynamicVirtualAttributes.html',
-            scope: {
-              dynamicForm: "=form",
-              user: "="
-            },
-            controller: function ($scope) {
+            var getTemplateUrl = function () {
+              return 'views/dynamicVirtualAttributes.html';
+            };
+            return {
+              restrict: 'E',
+              templateUrl: getTemplateUrl(),
+              scope: {
+                dynamicForm: "=form",
+                user: "="
+              },
+              controller: function ($scope) {
+               
+                $scope.getByGroup = function (schema) {
+                  var currentVirSchemas = new Array();
+                  for (var i = 0; i < $scope.dynamicForm.virSchemas.length; i++) {
+                    if (schema == "own" && $scope.dynamicForm.virSchemas[i].key.indexOf('#') == -1) {
+                      var attr = $scope.dynamicForm.virSchemas[i];
+                      attr.simpleKey = $scope.dynamicForm.virSchemas[i].key;
+                      currentVirSchemas.push($scope.dynamicForm.virSchemas[i]);
+                    }
+                    if ($scope.dynamicForm.virSchemas[i].key.indexOf(schema) > -1) {
+                      var attr = $scope.dynamicForm.virSchemas[i];
+                      attr.simpleKey = $scope.dynamicForm.virSchemas[i].key.substring($scope.dynamicForm.virSchemas[i].key.indexOf("#") + 1);
+                      currentVirSchemas.push(attr);
+                    }
+                  }
+                  return currentVirSchemas;
+                };
 
-              $scope.addVirtualAttributeField = function (virSchemaKey) {
-                console.debug("Add VIRTUAL value:", virSchemaKey);
-                console.debug(" ", ($scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.length));
-                $scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.push(virSchemaKey + "_" + ($scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.length));
-              };
+                $scope.addVirtualAttributeField = function (virSchemaKey) {
+                  console.debug("Add VIRTUAL value:", virSchemaKey);
+                  console.debug(" ", ($scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.length));
+                  $scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.push(virSchemaKey + "_" + ($scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.length));
+                };
 
-              $scope.removeVirtualAttributeField = function (virSchemaKey, index) {
-                console.debug("Remove VIRTUAL value: ", virSchemaKey);
-                console.debug("Remove VIRTUAL value: ", index);
-                $scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.splice(index, 1);
-                // clean user model
-                $scope.user.virAttrs[virSchemaKey].values.splice(index, 1);
-              };
-            }
-          };
-        });
+                $scope.removeVirtualAttributeField = function (virSchemaKey, index) {
+                  console.debug("Remove VIRTUAL value: ", virSchemaKey);
+                  console.debug("Remove VIRTUAL value: ", index);
+                  $scope.dynamicForm.virtualAttributeTable[virSchemaKey].fields.splice(index, 1);
+                  // clean user model
+                  $scope.user.virAttrs[virSchemaKey].values.splice(index, 1);
+                };
+              }
+            };
+          });

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/directives/groups.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/groups.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/groups.js
index 1f4fa4b..52b1d0c 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/groups.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/groups.js
@@ -28,7 +28,6 @@ angular.module('self')
               user: "="
             },
             controller: function ($scope, $filter) {
-
               $scope.init = function () {
                 if (!$scope.user.memberships) {
                   $scope.user.memberships = new Array();
@@ -37,22 +36,25 @@ angular.module('self')
               };
 
               $scope.addGroup = function (item, model) {
-                var group = item;
-                $scope.user.memberships.push({"rightKey": group.rightKey});                
+                var membership = item;
+                $scope.user.memberships.push({"rightKey": membership.rightKey, "groupName": membership.groupName});
+                $scope.$emit("groupAdded", membership.groupName);
               };
 
               $scope.removeGroup = function (item, model) {
                 var groupIndex = $scope.getIndex(item);
-                $scope.user.memberships.splice(groupIndex, 1);                
+                var membership = $scope.user.memberships[groupIndex];
+                var groupName = membership.groupName;
+                $scope.user.memberships.splice(groupIndex, 1);
+                $scope.$emit("groupRemoved", groupName);
               };
 
               $scope.getIndex = function (selectedGroup) {
-                var groupIndex = $scope.user.memberships.map(function (groupName) {
-                  return groupName;
-                }).indexOf(selectedGroup);
+                var groupIndex = $scope.user.memberships.map(function (membership) {
+                  return membership.groupName;
+                }).indexOf(selectedGroup.groupName);
                 return groupIndex;
               };
-
             }
           };
         });

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/directives/validationMessage.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/validationMessage.js b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/validationMessage.js
index 3fc8b33..158578d 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/directives/validationMessage.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/directives/validationMessage.js
@@ -27,7 +27,7 @@ angular.module('SyncopeEnduserApp').
               "name": "@",
               "template": "@"
             },
-            require: '^form',
+            require: '^?form',
             replace: false,
             link: function (scope, el, attrs, formCtrl) {
               var popupMessage = "Data are invalid: please correct accordingly";

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
index 7982a2e..2d86dac 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
+++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js
@@ -26,10 +26,21 @@ angular.module('self')
             var schemaService = {};
 
             schemaService.getUserSchemas = function (anyTypeClass) {
+              var param = anyTypeClass ? "?anyTypeClass=" + encodeURI(anyTypeClass) : "";
 
-              var classParam = anyTypeClass ? "?anyTypeClass=" + encodeURI(anyTypeClass) : "";
+              return  $http.get("/syncope-enduser/api/schemas" + param)
+                      .then(function (response) {
+                        return response.data;
+                      }, function (response) {
+                        console.error("Something went wrong during schema retrieval, exit with status: ", response);
+                        return $q.reject(response.data || response.statusText);
+                      });
+            };
 
-              return  $http.get("/syncope-enduser/api/schemas" + classParam)
+            schemaService.getTypeExtSchemas = function (group) {
+              var param = group ? "?group=" + encodeURI(group) : "";
+
+              return  $http.get("/syncope-enduser/api/schemas" + param)
                       .then(function (response) {
                         return response.data;
                       }, function (response) {
@@ -37,6 +48,7 @@ angular.module('self')
                         return $q.reject(response.data || response.statusText);
                       });
             };
+
             return schemaService;
           }]);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json
index 59e6239..d5a1e46 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json
+++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/de/static.json
@@ -41,7 +41,9 @@
   "SUCCESSFULLY_CREATED": "succes aangemaakt",
   "SUCCESSFULLY_UPDATED": "succesvol ge├╝pdatet",
   "PASSWORD_UPDATED": ": password succesfully updated",
-  "GOBACKHOME": "Klik op deze link om terug te gaan naar de home page"
+  "GOBACKHOME": "Klik op deze link om terug te gaan naar de home page",
+  "own": "Besitzen"
+
 
 }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json
index a8aa5a8..007cf48 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json
+++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/en/static.json
@@ -41,6 +41,8 @@
   "SUCCESSFULLY_CREATED": "succesfully created",
   "SUCCESSFULLY_UPDATED": "succesfully updated",
   "PASSWORD_UPDATED": ": password succesfully updated",
-  "GOBACKHOME": "Click on this link to go back to the home page"
+  "GOBACKHOME": "Click on this link to go back to the home page",
+  "own": "Own"
+
 }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json b/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json
index f098ca6..a5921b4 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json
+++ b/client/enduser/src/main/resources/META-INF/resources/app/languages/it/static.json
@@ -40,5 +40,6 @@
   "SUCCESSFULLY_CREATED": "creato con successo",
   "SUCCESSFULLY_UPDATED": "aggiornato con successo",
   "PASSWORD_UPDATED": ": password succesfully updated",
-  "GOBACKHOME": "Clicca su questo link per tornare alla home page"
+  "GOBACKHOME": "Clicca su questo link per tornare alla home page",
+  "own": "Propri"
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
index fa10d54..baf1227 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicDerivedAttributes.html
@@ -16,9 +16,35 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<div id="attribute" class="form-group" ng-repeat="derSchema in dynamicForm.derSchemas">
-  <label for="derivedSchema.key">{{derSchema.key}}</label>  <span uib-popover="{{derSchema.expression}}" 
-                                                                  popover-trigger="mouseenter" 
-                                                                  class="glyphicon glyphicon-question-sign"/>
-  <dynamic-derived-attribute schema="derSchema" user="user" index="0"></dynamic-derived-attribute>
-</div>
+<div ng-repeat="groupSchema in dynamicForm.groupSchemas">
+  <uib-accordion>
+    <div uib-accordion-group heading="{{groupSchema| translate}}"
+         ng-init="status = {isOpen: (groupSchema == 'own')}" is-open="status.isOpen"
+         class="breadcrumb-header">
+      <div id="attribute" class="form-group" ng-repeat="derSchema in getByGroup(groupSchema) track by $index">
+        <label for="derSchema.key">{{derSchema.simpleKey| translate}} <span ng-if="derSchema.mandatoryCondition === 'true'">*</span></label>
+        <div ng-if="!derSchema.multivalue">
+          <dynamic-derived-attribute schema="derSchema" user="user" index="0"></dynamic-derived-attribute>
+          <validation-message name="{{derSchema.key| translate}}"/>
+        </div>
+        <div ng-if="derSchema.multivalue">
+          <div ng-repeat="field in dynamicForm.attributeTable[derSchema.key].fields track by $index" 
+               ng-model='dynamicForm.attributeTable[derSchema.key].fields[$index]'>
+            <dynamic-derived-attribute schema="derSchema" user="user" index="$index"></dynamic-derived-attribute>
+            <span>
+              <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" 
+                      ng-click="removeAttributeField(derSchema.key, $index)">
+                <i class="glyphicon glyphicon-minus" title="Remove value"></i>
+              </button>
+            </span>
+          </div>
+          <span>
+            <button class="btn btn-default btn-sm" type="button" ng-click="addAttributeField(derSchema.key)">
+              <i class="glyphicon glyphicon-plus" title="Add value"></i>
+            </button>
+          </span>
+        </div>
+      </div>
+    </div>
+  </uib-accordion>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
index 9d3ea20..81ebc6d 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicPlainAttributes.html
@@ -16,28 +16,34 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<div id="attribute" class="form-group" ng-repeat="plainSchema in dynamicForm.plainSchemas">
-  <label for="plainSchema.key">{{plainSchema.key| translate}} <span ng-if="plainSchema.mandatoryCondition === 'true'">*</span></label>
-  <div ng-if="!plainSchema.multivalue">
-    <dynamic-plain-attribute schema="plainSchema" user="user" index="0"></dynamic-plain-attribute>
-    <validation-message name="{{plainSchema.key| translate}}"/>
-  </div>
 
-  <div ng-if="plainSchema.multivalue">
-    <div ng-repeat="field in dynamicForm.attributeTable[plainSchema.key].fields track by $index" 
-         ng-model='dynamicForm.attributeTable[plainSchema.key].fields[$index]'>
-      <div class="multivalue">
-        <button class="btn btn-default btn-sm" type="button" ng-click="addAttributeField(plainSchema.key)">
-          <i class="glyphicon glyphicon-plus" title="Add value"></i>
-        </button>
-        <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" 
-                ng-click="removeAttributeField(plainSchema.key, $index)">
-          <i class="glyphicon glyphicon-minus" title="Remove value"></i>
-        </button>
-        <dynamic-plain-attribute schema="plainSchema" user="user" index="$index"></dynamic-plain-attribute>
+<div ng-repeat="groupSchema in dynamicForm.groupSchemas">
+  <uib-accordion>
+    <div uib-accordion-group heading="{{groupSchema| translate}}"
+         ng-init="status = {isOpen: (groupSchema == 'own')}" is-open="status.isOpen"
+         class="breadcrumb-header">
+      <div id="attribute" class="form-group" ng-repeat="plainSchema in getByGroup(groupSchema) track by $index">
+        <label for="plainSchema.key">{{plainSchema.simpleKey| translate}} <span ng-if="plainSchema.mandatoryCondition === 'true'">*</span></label>
+        <div ng-if="!plainSchema.multivalue">
+          <dynamic-plain-attribute schema="plainSchema" user="user" index="0"></dynamic-plain-attribute>
+          <validation-message name="{{plainSchema.key| translate}}"/>
+        </div>
+        <div ng-if="plainSchema.multivalue">
+          <div ng-repeat="field in dynamicForm.attributeTable[plainSchema.key].fields track by $index" 
+               ng-model='dynamicForm.attributeTable[plainSchema.key].fields[$index]'>
+            <div class="multivalue">
+              <button class="btn btn-default btn-sm" type="button" ng-click="addAttributeField(plainSchema.key)">
+                <i class="glyphicon glyphicon-plus" title="Add value"></i>
+              </button>
+              <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" 
+                      ng-click="removeAttributeField(plainSchema.key, $index)">
+                <i class="glyphicon glyphicon-minus" title="Remove value"></i>
+              </button>
+              <dynamic-plain-attribute schema="plainSchema" user="user" index="$index"></dynamic-plain-attribute>
+            </div>
+          </div>
+        </div>
       </div>
-
     </div>
-
-  </div>
-</div>
+  </uib-accordion>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
----------------------------------------------------------------------
diff --git a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
index cd4750f..4dcca94 100644
--- a/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
+++ b/client/enduser/src/main/resources/META-INF/resources/app/views/dynamicVirtualAttributes.html
@@ -16,21 +16,32 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-<div id="attribute" class="form-group" ng-repeat="virtualSchema in dynamicForm.virSchemas">
-  <label for="virtualSchema.key">{{virtualSchema.key}}</label>
-  <!--all virtual schemas are multivalue-->
-  <div ng-repeat="field in dynamicForm.virtualAttributeTable[virtualSchema.key].fields track by $index" 
-       ng-model='dynamicForm.virtualAttributeTable[virtualSchema.key].fields[$index]'>    
-    <div class="multivalue">
-      <button class="btn btn-default btn-sm" type="button" ng-click="addVirtualAttributeField(virtualSchema.key)" 
-              ng-show="!virtualSchema.readonly">
-        <i class="glyphicon glyphicon-plus" title="Add value"></i>
-      </button>
-      <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" ng-show="!virtualSchema.readonly"
-              ng-click="removeVirtualAttributeField(virtualSchema.key, $index)">
-        <i class="glyphicon glyphicon-minus" title="Remove value"></i>
-      </button>
-      <dynamic-virtual-attribute schema="virtualSchema" user="user" index="$index"></dynamic-virtual-attribute>
+<div  ng-repeat="groupSchema in dynamicForm.groupSchemas">
+  <uib-accordion ng-if="getByGroup(groupSchema).length > 0">
+    <div uib-accordion-group heading="{{groupSchema| translate}}"
+         ng-init="status = {isOpen: (groupSchema == 'own')}" is-open="status.isOpen"
+         class="breadcrumb-header">
+      <div id="attribute" class="form-group" ng-repeat="virSchema in getByGroup(groupSchema) track by $index">
+        <label for="virSchema.key">{{virSchema.simpleKey| translate}} <span ng-if="virSchema.mandatoryCondition === 'true'">*</span></label>
+        <div ng-if="!virSchema.multivalue">
+          <dynamic-virtual-attribute schema="virSchema" user="user" index="0"></dynamic-virtual-attribute>
+          <validation-message name="{{virSchema.key| translate}}"/>
+        </div>
+        <div ng-if="virSchema.multivalue">
+          <div ng-repeat="field in dynamicForm.attributeTable[virSchema.key].fields track by $index" 
+               ng-model='dynamicForm.attributeTable[virSchema.key].fields[$index]'>
+            <div class="multivalue">
+              <dynamic-virtual-attribute schema="virSchema" user="user" index="$index"></dynamic-virtual-attribute>
+              <button class="btn btn-default btn-sm minus" ng-if="$index > 0" type="button" 
+                      ng-click="removeAttributeField(virSchema.key, $index)">
+                <i class="glyphicon glyphicon-minus" title="Remove value"></i>
+              </button><button class="btn btn-default btn-sm" type="button" ng-click="addAttributeField(virSchema.key)">
+                <i class="glyphicon glyphicon-plus" title="Add value"></i>
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
-  </div>
-</div>
+  </uib-accordion>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GroupService.java
----------------------------------------------------------------------
diff --git a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GroupService.java b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GroupService.java
index 549b41a..9ec56b2 100644
--- a/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GroupService.java
+++ b/common/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GroupService.java
@@ -29,6 +29,7 @@ import javax.ws.rs.core.MediaType;
 import org.apache.syncope.common.lib.patch.GroupPatch;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.types.BulkMembersActionType;
 
 /**
@@ -49,6 +50,20 @@ public interface GroupService extends AnyService<GroupTO, GroupPatch> {
     List<GroupTO> own();
 
     /**
+     * Extracts type extension information, for the provided group and any type.
+     *
+     * @param key group key
+     * @param anyTypeKey any type key
+     * @return type extension information, for the provided group and any type
+     */
+    @GET
+    @Path("{key}/{anyTypeKey}/typeExtension")
+    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+    TypeExtensionTO readTypeExtension(
+            @NotNull @PathParam("key") String key,
+            @NotNull @PathParam("anyTypeKey") String anyTypeKey);
+
+    /**
      * (De)provision all members of the given group from / onto all the resources associated to it.
      *
      * @param key group key

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
----------------------------------------------------------------------
diff --git a/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java b/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
index c3a2df3..bd07a22 100644
--- a/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
+++ b/core/logic/src/main/java/org/apache/syncope/core/logic/GroupLogic.java
@@ -40,6 +40,7 @@ import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.PropagationStatus;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.BulkMembersActionType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
@@ -152,6 +153,18 @@ public class GroupLogic extends AbstractAnyLogic<GroupTO, GroupPatch> {
 
     @PreAuthorize("isAuthenticated()")
     @Transactional(readOnly = true)
+    public TypeExtensionTO readTypeExtension(final String key, final String anyTypeKey) {
+        Group group = groupDAO.find(key);
+        if (group == null) {
+            throw new NotFoundException("Group " + key);
+        }
+
+        GroupTO groupTO = binder.getGroupTO(group, false);
+        return groupTO.getTypeExtension(anyTypeKey);
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Transactional(readOnly = true)
     @Override
     public int count(final String realm) {
         return groupDAO.count(getEffectiveRealms(SyncopeConstants.FULL_ADMIN_REALMS, realm));

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
index abf17c0..0a477cf 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AnyObjectDataBinderImpl.java
@@ -215,9 +215,12 @@ public class AnyObjectDataBinderImpl extends AbstractAnyDataBinder implements An
                     searchDAO.search(SearchCond.getLeafCond(assignableCond), AnyTypeKind.GROUP);
 
             for (MembershipTO membershipTO : anyObjectTO.getMemberships()) {
-                Group group = groupDAO.find(membershipTO.getRightKey());
+                Group group = membershipTO.getRightKey() == null
+                        ? groupDAO.findByName(membershipTO.getGroupName())
+                        : groupDAO.find(membershipTO.getRightKey());
                 if (group == null) {
-                    LOG.debug("Ignoring invalid group " + membershipTO.getGroupName());
+                    LOG.debug("Ignoring invalid group "
+                            + membershipTO.getRightKey() + " / " + membershipTO.getGroupName());
                 } else if (assignableGroups.contains(group)) {
                     AMembership membership = entityFactory.newEntity(AMembership.class);
                     membership.setRightEnd(group);

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
----------------------------------------------------------------------
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
index 998514a..11396d7 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/UserDataBinderImpl.java
@@ -231,9 +231,12 @@ public class UserDataBinderImpl extends AbstractAnyDataBinder implements UserDat
                     searchDAO.search(SearchCond.getLeafCond(assignableCond), AnyTypeKind.GROUP);
 
             for (MembershipTO membershipTO : userTO.getMemberships()) {
-                Group group = groupDAO.find(membershipTO.getRightKey());
+                Group group = membershipTO.getRightKey() == null
+                        ? groupDAO.findByName(membershipTO.getGroupName())
+                        : groupDAO.find(membershipTO.getRightKey());
                 if (group == null) {
-                    LOG.debug("Ignoring invalid group " + membershipTO.getGroupName());
+                    LOG.debug("Ignoring invalid group "
+                            + membershipTO.getRightKey() + " / " + membershipTO.getGroupName());
                 } else if (assignableGroups.contains(group)) {
                     UMembership membership = entityFactory.newEntity(UMembership.class);
                     membership.setRightEnd(group);

http://git-wip-us.apache.org/repos/asf/syncope/blob/343b3d40/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
----------------------------------------------------------------------
diff --git a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
index 50ef081..69b21a1 100644
--- a/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
+++ b/core/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GroupServiceImpl.java
@@ -22,6 +22,7 @@ import java.util.List;
 import org.apache.syncope.common.lib.patch.GroupPatch;
 import org.apache.syncope.common.lib.to.ExecTO;
 import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.TypeExtensionTO;
 import org.apache.syncope.common.lib.types.BulkMembersActionType;
 import org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.core.logic.AbstractAnyLogic;
@@ -53,6 +54,11 @@ public class GroupServiceImpl extends AbstractAnyService<GroupTO, GroupPatch> im
     }
 
     @Override
+    public TypeExtensionTO readTypeExtension(final String key, final String anyTypeKey) {
+        return logic.readTypeExtension(key, anyTypeKey);
+    }
+
+    @Override
     public ExecTO bulkMembersAction(final String key, final BulkMembersActionType actionType) {
         return logic.bulkMembersAction(key, actionType);
     }


Mime
View raw message