From cd5726d6a01caf5fd0ffbff798f42eb0ffa308c4 Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Wed, 7 Aug 2019 17:16:26 +0200
Subject: [PATCH] Removed JPA and used Postgresql ltree extension

---
 gms/pom.xml                                   |  13 +-
 .../it/inaf/ia2/gms/authn/SessionData.java    |  11 -
 .../ia2/gms/controller/GroupsController.java  |  40 ++--
 .../gms/controller/PermissionsController.java |  10 -
 .../java/it/inaf/ia2/gms/model/GroupNode.java |  52 +----
 .../ia2/gms/model/GroupsModelResponse.java    |  10 +-
 .../it/inaf/ia2/gms/model/Permission.java     |  28 ++-
 .../inaf/ia2/gms/persistence/GroupsDAO.java   | 100 +++++++-
 .../ia2/gms/persistence/GroupsRepository.java |  14 --
 .../gms/persistence/MembershipRepository.java |  19 --
 .../ia2/gms/persistence/MembershipsDAO.java   |  13 ++
 .../ia2/gms/persistence/PermissionsDAO.java   |  71 +++++-
 .../persistence/PermissionsRepository.java    |  25 --
 .../ia2/gms/persistence/UsersRepository.java  |  11 -
 .../inaf/ia2/gms/persistence/model/Group.java |  95 --------
 .../gms/persistence/model/GroupEntity.java    |  77 +++++++
 .../ia2/gms/persistence/model/Membership.java |  44 ----
 .../persistence/model/MembershipEntity.java   |  23 ++
 .../gms/persistence/model/MembershipId.java   |  39 ----
 ...rPermission.java => PermissionEntity.java} |  13 +-
 .../inaf/ia2/gms/persistence/model/User.java  |  23 --
 .../model/UserGroupPermission.java            |  95 --------
 .../model/UserGroupPermissionId.java          |  45 ----
 .../ia2/gms/service/GroupsModelBuilder.java   |  62 +++++
 .../ia2/gms/service/GroupsModelService.java   |  86 -------
 .../inaf/ia2/gms/service/GroupsService.java   | 213 +++++-------------
 .../ia2/gms/service/GroupsTreeBuilder.java    |  82 +++++++
 .../inaf/ia2/gms/service/MembersService.java  |  14 +-
 .../inaf/ia2/gms/service/PermissionUtils.java |  40 ++++
 .../ia2/gms/service/PermissionsService.java   |  35 ++-
 .../it/inaf/ia2/gms/service/UsersService.java |  24 --
 gms/src/main/resources/application.properties |   1 +
 gms/src/main/resources/sql/init.sql           |   6 +-
 .../gms/controller/GroupsControllerTest.java  |   4 +-
 .../ia2/gms/persistence/GroupEntityTest.java  |  28 +++
 .../ia2/gms/persistence/GroupsDAOTest.java    |  48 +++-
 .../NestedGroupsIntegrationTest.java          | 120 +++++-----
 .../gms/persistence/PermissionsDAOTest.java   |  19 +-
 .../PermissionsServiceIntegrationTest.java    |  45 ++--
 39 files changed, 758 insertions(+), 940 deletions(-)
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsRepository.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/UsersRepository.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipEntity.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java
 rename gms/src/main/java/it/inaf/ia2/gms/persistence/model/{UserPermission.java => PermissionEntity.java} (69%)
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelBuilder.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/PermissionUtils.java
 delete mode 100644 gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java
 create mode 100644 gms/src/test/java/it/inaf/ia2/gms/persistence/GroupEntityTest.java

diff --git a/gms/pom.xml b/gms/pom.xml
index 905ed51..f55c143 100644
--- a/gms/pom.xml
+++ b/gms/pom.xml
@@ -34,14 +34,14 @@
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jpa</artifactId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
         </dependency>
         <dependency>
-            <groupId>mysql</groupId>
-            <artifactId>mysql-connector-java</artifactId>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>42.2.6</version>
             <scope>runtime</scope>
         </dependency>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
@@ -58,11 +58,6 @@
             <artifactId>spring-security-test</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>com.h2database</groupId>
-            <artifactId>h2</artifactId>
-            <scope>test</scope>
-        </dependency>
         <dependency>
             <groupId>io.zonky.test</groupId>
             <artifactId>embedded-database-spring-test</artifactId>
diff --git a/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java
index b3fd20a..bc41752 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/authn/SessionData.java
@@ -1,7 +1,5 @@
 package it.inaf.ia2.gms.authn;
 
-import it.inaf.ia2.gms.persistence.UsersRepository;
-import it.inaf.ia2.gms.persistence.model.User;
 import javax.annotation.PostConstruct;
 import javax.servlet.http.HttpServletRequest;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -16,9 +14,6 @@ public class SessionData {
     @Autowired
     private HttpServletRequest request;
 
-    @Autowired
-    private UsersRepository usersRepository;
-
     private String userId;
     private String accessToken;
 
@@ -27,12 +22,6 @@ public class SessionData {
         CustomAuthenticationData authn = (CustomAuthenticationData) ((OAuth2Authentication) request.getUserPrincipal()).getUserAuthentication();
         userId = (String) authn.getPrincipal();
         accessToken = (String) authn.getAccessToken().getValue();
-
-        if (!usersRepository.findById(userId).isPresent()) {
-            User user = new User();
-            user.setId(userId);
-            usersRepository.save(user);
-        }
     }
 
     public String getUserId() {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
index 22c93cb..8eb6f64 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/GroupsController.java
@@ -8,12 +8,10 @@ import it.inaf.ia2.gms.model.GroupsModelResponse;
 import it.inaf.ia2.gms.model.PaginatedData;
 import it.inaf.ia2.gms.model.PaginatedModelRequest;
 import it.inaf.ia2.gms.model.RenameGroupRequest;
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.service.GroupsModelService;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.service.GroupsModelBuilder;
 import it.inaf.ia2.gms.service.GroupsService;
-import it.inaf.ia2.gms.service.UsersService;
-import java.util.List;
+import it.inaf.ia2.gms.service.GroupsTreeBuilder;
 import javax.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
@@ -34,13 +32,13 @@ public class GroupsController {
     private SessionData session;
 
     @Autowired
-    private UsersService usersService;
+    private GroupsService groupsService;
 
     @Autowired
-    private GroupsModelService groupsModelService;
+    private GroupsModelBuilder groupsModelService;
 
     @Autowired
-    private GroupsService groupsService;
+    private GroupsTreeBuilder groupsTreeBuilder;
 
     @GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public GroupsModelResponse getGroupsModelResponse(@Valid GroupsModelRequest groupsModelRequest) {
@@ -50,10 +48,9 @@ public class GroupsController {
     @PostMapping(value = "/group", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<GroupNode>> createGroup(@Valid @RequestBody CreateGroupRequest request) {
 
-        User user = getUser();
-        Group newGroup = groupsService.addGroup(request.getParentGroupId(), request.getNewGroupName(), user);
+        GroupEntity newGroup = groupsService.addGroup(request.getParentGroupId(), request.getNewGroupName(), session.getUserId());
 
-        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(newGroup.getParentGroup(), request, user);
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(request.getParentGroupId(), request);
 
         return new ResponseEntity<>(groupsPanel, HttpStatus.CREATED);
     }
@@ -61,10 +58,11 @@ public class GroupsController {
     @PutMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<PaginatedData<GroupNode>> renameGroup(@PathVariable("groupId") String groupId, @Valid @RequestBody RenameGroupRequest request) {
 
-        User user = getUser();
-        Group renamedGroup = groupsService.renameGroup(groupId, request.getNewGroupName(), user);
+        GroupEntity renamedGroup = groupsService.renameGroup(groupId, request.getNewGroupName(), session.getUserId());
+
+        GroupEntity parent = groupsService.getGroupByPath(renamedGroup.getParentPath());
 
-        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(renamedGroup.getParentGroup(), request, user);
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent.getId(), request);
 
         return ResponseEntity.ok(groupsPanel);
     }
@@ -72,20 +70,14 @@ public class GroupsController {
     @DeleteMapping(value = "/group/{groupId}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity<?> deleteGroup(@PathVariable("groupId") String groupId, PaginatedModelRequest request) {
 
-        User user = getUser();
-        Group parent = groupsService.deleteGroup(groupId, user);
+        GroupEntity parent = groupsService.deleteGroup(groupId, session.getUserId());
 
-        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent, request, user);
+        PaginatedData<GroupNode> groupsPanel = getGroupsPanel(parent.getId(), request);
 
         return ResponseEntity.ok(groupsPanel);
     }
 
-    private User getUser() {
-        return usersService.getUserById(session.getUserId());
-    }
-
-    private PaginatedData<GroupNode> getGroupsPanel(Group parentGroup, PaginatedModelRequest paginatedRequest, User user) {
-        List<GroupNode> groupNodes = groupsService.getSubgroups(parentGroup, user);
-        return new PaginatedData<>(groupNodes, paginatedRequest.getPaginatorPage(), paginatedRequest.getPaginatorPageSize());
+    private PaginatedData<GroupNode> getGroupsPanel(String parentGroupId, PaginatedModelRequest paginatedRequest) {
+        return groupsTreeBuilder.listSubGroups(parentGroupId, session.getUserId(), paginatedRequest);
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java
index 4ae3d1c..9c204e7 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/controller/PermissionsController.java
@@ -2,9 +2,7 @@ package it.inaf.ia2.gms.controller;
 
 import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.persistence.model.User;
 import it.inaf.ia2.gms.service.PermissionsService;
-import it.inaf.ia2.gms.service.UsersService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
@@ -18,19 +16,11 @@ public class PermissionsController {
     @Autowired
     private SessionData session;
 
-    @Autowired
-    private UsersService usersService;
-    
     @Autowired
     private PermissionsService permissionsService;
 
     @DeleteMapping(value = "/permission", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
     public ResponseEntity deletePermission(@RequestParam("user") String userId, @RequestParam("group") String group, @RequestParam("permission") Permission permission) {
-        User user = getUser();
         return null;
     }
-
-    private User getUser() {
-        return usersService.getUserById(session.getUserId());
-    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
index 6ca71a1..cc1316b 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupNode.java
@@ -1,22 +1,12 @@
 package it.inaf.ia2.gms.model;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 public class GroupNode {
 
     private String groupId;
     private String groupName;
-    private Set<Permission> permissions;
+    private Permission permission;
     private boolean hasChildren;
 
-    public GroupNode() {
-        permissions = new HashSet<>();
-    }
-
     public String getGroupId() {
         return groupId;
     }
@@ -33,44 +23,12 @@ public class GroupNode {
         this.groupName = groupName;
     }
 
-    public List<Permission> getPermissions() {
-        return Collections.unmodifiableList(new ArrayList<>(permissions));
+    public Permission getPermission() {
+        return permission;
     }
 
-    public void setPermissions(Set<Permission> permissions) {
-        this.permissions = permissions;
-    }
-
-    public void addPermission(Permission permission) {
-
-        // remove implied permissions
-        switch (permission) {
-            case ADMIN:
-                permissions.add(permission);
-                permissions.remove(Permission.MANAGE_MEMBERS);
-                permissions.remove(Permission.VIEW_MEMBERS);
-                permissions.remove(Permission.TRAVERSE);
-                break;
-            case MANAGE_MEMBERS:
-                if (!permissions.contains(Permission.ADMIN)) {
-                    permissions.add(permission);
-                    permissions.remove(Permission.VIEW_MEMBERS);
-                    permissions.remove(Permission.TRAVERSE);
-                }
-                break;
-            case VIEW_MEMBERS:
-                if (!permissions.contains(Permission.ADMIN)
-                        && !permissions.contains(Permission.MANAGE_MEMBERS)) {
-                    permissions.add(permission);
-                    permissions.remove(Permission.TRAVERSE);
-                }
-                break;
-            case TRAVERSE:
-                if (permissions.isEmpty()) {
-                    permissions.add(permission);
-                }
-                break;
-        }
+    public void setPermission(Permission permission) {
+        this.permission = permission;
     }
 
     public boolean isHasChildren() {
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java
index 415ff5c..25cef04 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/GroupsModelResponse.java
@@ -9,7 +9,7 @@ public class GroupsModelResponse extends BaseModelResponse {
     private PaginatedData<RapUser> membersPanel;
     private PaginatedData<UserPermission> permissionsPanel;
     // current group permissions
-    private List<Permission> permissions;
+    private Permission permission;
 
     public List<GroupBreadcrumb> getBreadcrumbs() {
         return breadcrumbs;
@@ -43,11 +43,11 @@ public class GroupsModelResponse extends BaseModelResponse {
         this.permissionsPanel = permissionsPanel;
     }
 
-    public List<Permission> getPermissions() {
-        return permissions;
+    public Permission getPermission() {
+        return permission;
     }
 
-    public void setPermissions(List<Permission> permissions) {
-        this.permissions = permissions;
+    public void setPermission(Permission permission) {
+        this.permission = permission;
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java b/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java
index be00ef7..166c1dc 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/model/Permission.java
@@ -5,5 +5,31 @@ public enum Permission {
     ADMIN,
     MANAGE_MEMBERS,
     VIEW_MEMBERS,
-    TRAVERSE
+    TRAVERSE;
+
+    /**
+     * Updates the permission keeping the priority (e.g. MANAGE_MEMBERS
+     * overrides VIEW_MEMBERS, but not the opposite).
+     */
+    public static Permission addPermission(Permission oldPermission, Permission newPermission) {
+
+        if (oldPermission == null) {
+            return newPermission;
+        }
+
+        switch (newPermission) {
+            case ADMIN:
+                return ADMIN;
+            case MANAGE_MEMBERS:
+                if (oldPermission != Permission.ADMIN) {
+                    return MANAGE_MEMBERS;
+                }
+            case VIEW_MEMBERS:
+                if (oldPermission == Permission.TRAVERSE) {
+                    return VIEW_MEMBERS;
+                }
+        }
+
+        return oldPermission;
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
index f1109fa..5a38bca 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
@@ -1,12 +1,14 @@
 package it.inaf.ia2.gms.persistence;
 
-import it.inaf.ia2.gms.persistence.model.NewGroup;
+import it.inaf.ia2.gms.model.GroupBreadcrumb;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import java.sql.PreparedStatement;
 import java.sql.Types;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import javax.sql.DataSource;
@@ -24,7 +26,7 @@ public class GroupsDAO {
         jdbcTemplate = new JdbcTemplate(dataSource);
     }
 
-    public NewGroup createGroup(NewGroup group) {
+    public GroupEntity createGroup(GroupEntity group) {
 
         String sql = "INSERT INTO gms_group (id, name, path) VALUES (?, ?, ?)";
 
@@ -39,12 +41,67 @@ public class GroupsDAO {
         return group;
     }
 
+    public GroupEntity updateGroup(GroupEntity group) {
+
+        String sql = "UPDATE gms_group SET name = ?, path = ? WHERE id = ?";
+
+        jdbcTemplate.update(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, group.getName());
+            ps.setObject(2, group.getPath(), Types.OTHER);
+            ps.setString(3, group.getId());
+            return ps;
+        });
+
+        return group;
+    }
+
     public void deleteGroupById(String groupId) {
         String sql = "DELETE FROM gms_group WHERE id = ?";
         jdbcTemplate.update(sql, groupId);
     }
 
-    public List<NewGroup> listSubGroups(String path) {
+    public Optional<GroupEntity> findGroupById(String groupId) {
+
+        String sql = "SELECT id, name, path from gms_group WHERE id = ?";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, groupId);
+            return ps;
+        }, resultSet -> {
+            if (resultSet.next()) {
+                GroupEntity group = new GroupEntity();
+                group.setId(resultSet.getString("id"));
+                group.setName(resultSet.getString("name"));
+                group.setPath(resultSet.getString("path"));
+                return Optional.of(group);
+            }
+            return Optional.empty();
+        });
+    }
+
+    public Optional<GroupEntity> findGroupByPath(String path) {
+
+        String sql = "SELECT id, name from gms_group WHERE path = ?";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setObject(1, path, Types.OTHER);
+            return ps;
+        }, resultSet -> {
+            if (resultSet.next()) {
+                GroupEntity group = new GroupEntity();
+                group.setId(resultSet.getString("id"));
+                group.setName(resultSet.getString("name"));
+                group.setPath(path);
+                return Optional.of(group);
+            }
+            return Optional.empty();
+        });
+    }
+
+    public List<GroupEntity> listSubGroups(String path) {
 
         String sql = "SELECT id, name, path from gms_group WHERE path ~ ? ORDER BY name";
 
@@ -53,9 +110,9 @@ public class GroupsDAO {
             ps.setObject(1, getSubGroupsPath(path), Types.OTHER);
             return ps;
         }, resultSet -> {
-            List<NewGroup> groups = new ArrayList<>();
+            List<GroupEntity> groups = new ArrayList<>();
             while (resultSet.next()) {
-                NewGroup group = new NewGroup();
+                GroupEntity group = new GroupEntity();
                 group.setId(resultSet.getString("id"));
                 group.setName(resultSet.getString("name"));
                 group.setPath(resultSet.getString("path"));
@@ -98,4 +155,37 @@ public class GroupsDAO {
             return map;
         });
     }
+
+    /**
+     * Returns the number of groups.
+     */
+    public long count() {
+
+        String sql = "SELECT COUNT(*) FROM gms_group";
+
+        return jdbcTemplate.query(sql, resultSet -> {
+            resultSet.next();
+            return resultSet.getLong(1);
+        });
+    }
+
+    public List<GroupBreadcrumb> getBreadcrumbs(String path) {
+
+        String sql = "SELECT id, name FROM gms_group WHERE path @> ? ORDER BY length(path::varchar) ASC";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setObject(1, path, Types.OTHER);
+            return ps;
+        }, resultSet -> {
+            List<GroupBreadcrumb> breadcrumbs = new ArrayList<>();
+            while (resultSet.next()) {
+                GroupBreadcrumb bc = new GroupBreadcrumb();
+                bc.setGroupId(resultSet.getString("id"));
+                bc.setGroupName(resultSet.getString("name"));
+                breadcrumbs.add(bc);
+            }
+            return breadcrumbs;
+        });
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsRepository.java
deleted file mode 100644
index c7ad1eb..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsRepository.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package it.inaf.ia2.gms.persistence;
-
-import it.inaf.ia2.gms.persistence.model.Group;
-import java.util.List;
-import javax.transaction.Transactional;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-@Transactional
-public interface GroupsRepository extends JpaRepository<Group, String> {
-
-    List<Group> findByParentGroup(Group parent);
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java
deleted file mode 100644
index 44bd7df..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipRepository.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package it.inaf.ia2.gms.persistence;
-
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.Membership;
-import it.inaf.ia2.gms.persistence.model.MembershipId;
-import it.inaf.ia2.gms.persistence.model.User;
-import java.util.List;
-import javax.transaction.Transactional;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-@Transactional
-public interface MembershipRepository extends JpaRepository<Membership, MembershipId> {
-
-    List<Membership> findBy_user(User user);
-
-    List<Membership> findBy_group(Group group);
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java
new file mode 100644
index 0000000..d6b832d
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/MembershipsDAO.java
@@ -0,0 +1,13 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.persistence.model.MembershipEntity;
+import java.util.List;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MembershipsDAO {
+
+    public List<MembershipEntity> findByGroup(String groupId) {
+        throw new UnsupportedOperationException("TODO");
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
index 0c48022..4162110 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
@@ -1,7 +1,7 @@
 package it.inaf.ia2.gms.persistence;
 
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.persistence.model.UserPermission;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
 import java.sql.PreparedStatement;
 import java.sql.Types;
 import java.util.ArrayList;
@@ -21,51 +21,100 @@ public class PermissionsDAO {
         jdbcTemplate = new JdbcTemplate(dataSource);
     }
 
-    public UserPermission createPermission(UserPermission userPermission) {
+    public PermissionEntity createPermission(PermissionEntity userPermission) {
 
-        String sql = "INSERT INTO gms_permission(group_id, user_id, permission) VALUES(?, ?, ?)";
+        String sql = "INSERT INTO gms_permission(group_id, user_id, permission, group_path) VALUES(?, ?, ?, ?)";
 
         jdbcTemplate.update(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
             ps.setString(1, userPermission.getGroupId());
             ps.setString(2, userPermission.getUserId());
             ps.setObject(3, userPermission.getPermission().toString(), Types.OTHER);
+            ps.setObject(4, userPermission.getGroupPath(), Types.OTHER);
             return ps;
         });
 
         return userPermission;
     }
 
-    public List<UserPermission> findUserPermissions(String userId) {
+    public List<PermissionEntity> findUserPermissions(String userId) {
 
-        String sql = "SELECT group_id, permission FROM gms_permission WHERE user_id = ?";
+        String sql = "SELECT group_id, permission, group_path FROM gms_permission WHERE user_id = ?";
 
         return jdbcTemplate.query(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
             ps.setString(1, userId);
             return ps;
         }, resultSet -> {
-            List<UserPermission> permissions = new ArrayList<>();
+            List<PermissionEntity> permissions = new ArrayList<>();
             while (resultSet.next()) {
-                UserPermission permission = new UserPermission();
+                PermissionEntity permission = new PermissionEntity();
                 permission.setGroupId(resultSet.getString("group_id"));
                 permission.setUserId(userId);
                 permission.setPermission(Permission.valueOf(resultSet.getString("permission")));
+                permission.setGroupPath(resultSet.getString("group_path"));
                 permissions.add(permission);
             }
             return permissions;
         });
     }
 
-    public void deletePermission(UserPermission userPermission) {
+    public List<PermissionEntity> findUserPermissions(String userId, String path) {
+
+        String sql = "SELECT group_id, permission, group_path FROM gms_permission WHERE user_id = ?\n"
+                + "AND (group_path <@ ? OR group_path @> ?)";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, userId);
+            ps.setObject(2, path, Types.OTHER);
+            ps.setObject(3, path, Types.OTHER);
+            return ps;
+        }, resultSet -> {
+            List<PermissionEntity> permissions = new ArrayList<>();
+            while (resultSet.next()) {
+                PermissionEntity permission = new PermissionEntity();
+                permission.setGroupId(resultSet.getString("group_id"));
+                permission.setUserId(userId);
+                permission.setPermission(Permission.valueOf(resultSet.getString("permission")));
+                permission.setGroupPath(resultSet.getString("group_path"));
+                permissions.add(permission);
+            }
+            return permissions;
+        });
+    }
+
+    public List<PermissionEntity> getGroupsPermissions(String groupId) {
+
+        String sql = "SELECT user_id, permission, group_path FROM gms_permission WHERE group_id = ?";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, groupId);
+            return ps;
+        }, resultSet -> {
+            List<PermissionEntity> permissions = new ArrayList<>();
+            while (resultSet.next()) {
+                PermissionEntity permission = new PermissionEntity();
+                permission.setGroupId(groupId);
+                permission.setUserId(resultSet.getString("user_id"));
+                permission.setPermission(Permission.valueOf(resultSet.getString("permission")));
+                permission.setGroupPath(resultSet.getString("group_path"));
+                permissions.add(permission);
+            }
+            return permissions;
+        });
+    }
+
+    public void deletePermission(String groupId, String userId, Permission permission) {
 
         String sql = "DELETE FROM gms_permission WHERE group_id = ? AND user_id = ? AND permission = ?";
 
         jdbcTemplate.update(conn -> {
             PreparedStatement ps = conn.prepareStatement(sql);
-            ps.setString(1, userPermission.getGroupId());
-            ps.setString(2, userPermission.getUserId());
-            ps.setObject(3, userPermission.getPermission().toString(), Types.OTHER);
+            ps.setString(1, groupId);
+            ps.setString(2, userId);
+            ps.setObject(3, permission.toString(), Types.OTHER);
             return ps;
         });
     }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java
deleted file mode 100644
index 9184992..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsRepository.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package it.inaf.ia2.gms.persistence;
-
-import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
-import it.inaf.ia2.gms.persistence.model.UserGroupPermissionId;
-import java.util.List;
-import java.util.Optional;
-import javax.transaction.Transactional;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.stereotype.Repository;
-
-@Repository
-@Transactional
-public interface PermissionsRepository extends JpaRepository<UserGroupPermission, UserGroupPermissionId> {
-
-    List<UserGroupPermission> findBy_user(User user);
-
-    List<UserGroupPermission> findBy_group(Group group);
-
-    @Query("SELECT u FROM UserGroupPermission u WHERE u._user.id = ?1 AND u._group.id = ?2 AND u.permission = ?3")
-    Optional<UserGroupPermission> findPermission(String userId, String groupId, Permission permission);
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/UsersRepository.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/UsersRepository.java
deleted file mode 100644
index bf60c63..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/UsersRepository.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package it.inaf.ia2.gms.persistence;
-
-import it.inaf.ia2.gms.persistence.model.User;
-import javax.transaction.Transactional;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-@Transactional
-public interface UsersRepository extends JpaRepository<User, String> {
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java
deleted file mode 100644
index 504093e..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Group.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package it.inaf.ia2.gms.persistence.model;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.FetchType;
-import javax.persistence.Id;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.OneToMany;
-import javax.persistence.OrderBy;
-import javax.persistence.Table;
-
-@Entity
-@Table(name = "gms_group")
-public class Group {
-
-    @Id
-    @Column(length = 50)
-    private String id;
-
-    @Column(nullable = false)
-    private String name;
-
-    @ManyToOne(fetch = FetchType.LAZY)
-    @JoinColumn(name = "parent_group")
-    private Group parentGroup;
-
-    @OneToMany(mappedBy = "parentGroup", fetch = FetchType.LAZY)
-    @OrderBy("name ASC")
-    private List<Group> childrenGroups;
-
-    public Group() {
-        childrenGroups = new ArrayList<>();
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    public Group getParentGroup() {
-        return parentGroup;
-    }
-
-    public void setParentGroup(Group parentGroup) {
-        this.parentGroup = parentGroup;
-    }
-
-    public List<Group> getChildrenGroups() {
-        return childrenGroups;
-    }
-
-    public void setChildrenGroups(List<Group> childrenGroups) {
-        this.childrenGroups = childrenGroups;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 3;
-        hash = 89 * hash + Objects.hashCode(this.id);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final Group other = (Group) obj;
-        if (!Objects.equals(this.id, other.id)) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java
new file mode 100644
index 0000000..f0563e3
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/GroupEntity.java
@@ -0,0 +1,77 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import java.util.Objects;
+
+public class GroupEntity {
+
+    private String id;
+    private String name;
+    private String path;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    public String getParentPath() {
+        if (path.isEmpty()) {
+            return null;
+        }
+        if (path.contains(".")) {
+            return path.substring(0, path.lastIndexOf("."));
+        }
+        return "";
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 5;
+        hash = 79 * hash + Objects.hashCode(this.id);
+        hash = 79 * hash + Objects.hashCode(this.name);
+        hash = 79 * hash + Objects.hashCode(this.path);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final GroupEntity other = (GroupEntity) obj;
+        if (!Objects.equals(this.id, other.id)) {
+            return false;
+        }
+        if (!Objects.equals(this.name, other.name)) {
+            return false;
+        }
+        if (!Objects.equals(this.path, other.path)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java
deleted file mode 100644
index 69b282f..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/Membership.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package it.inaf.ia2.gms.persistence.model;
-
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.IdClass;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.Table;
-
-/**
- * Note: if necessary this class could contain also a role (specific for the
- * application using the GMS). Currently that is not implemented.
- */
-@Entity
-@IdClass(MembershipId.class)
-@Table(name = "gms_membership")
-public class Membership {
-
-    @Id
-    @ManyToOne
-    @JoinColumn(name = "user_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
-    private User _user;
-
-    @Id
-    @ManyToOne
-    @JoinColumn(name = "group_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
-    private Group _group;
-
-    public User getUser() {
-        return _user;
-    }
-
-    public void setUser(User _user) {
-        this._user = _user;
-    }
-
-    public Group getGroup() {
-        return _group;
-    }
-
-    public void setGroup(Group _group) {
-        this._group = _group;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipEntity.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipEntity.java
new file mode 100644
index 0000000..9625a87
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipEntity.java
@@ -0,0 +1,23 @@
+package it.inaf.ia2.gms.persistence.model;
+
+public class MembershipEntity {
+
+    private String groupId;
+    private String userId;
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java
deleted file mode 100644
index c0b1bea..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/MembershipId.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package it.inaf.ia2.gms.persistence.model;
-
-import java.io.Serializable;
-import java.util.Objects;
-
-public class MembershipId implements Serializable {
-
-    String _user;
-    String _group;
-
-    @Override
-    public int hashCode() {
-        int hash = 7;
-        hash = 47 * hash + Objects.hashCode(this._user);
-        hash = 47 * hash + Objects.hashCode(this._group);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final MembershipId other = (MembershipId) obj;
-        if (!Objects.equals(this._user, other._user)) {
-            return false;
-        }
-        if (!Objects.equals(this._group, other._group)) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserPermission.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/PermissionEntity.java
similarity index 69%
rename from gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserPermission.java
rename to gms/src/main/java/it/inaf/ia2/gms/persistence/model/PermissionEntity.java
index 6d1e839..957e658 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserPermission.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/PermissionEntity.java
@@ -2,11 +2,14 @@ package it.inaf.ia2.gms.persistence.model;
 
 import it.inaf.ia2.gms.model.Permission;
 
-public class UserPermission {
+public class PermissionEntity {
 
     private String userId;
     private String groupId;
     private Permission permission;
+    
+    // The group path is copied here for performance reasons
+    private String groupPath;
 
     public String getUserId() {
         return userId;
@@ -31,4 +34,12 @@ public class UserPermission {
     public void setPermission(Permission permission) {
         this.permission = permission;
     }
+
+    public String getGroupPath() {
+        return groupPath;
+    }
+
+    public void setGroupPath(String groupPath) {
+        this.groupPath = groupPath;
+    }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java
deleted file mode 100644
index 9d2c6d7..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/User.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package it.inaf.ia2.gms.persistence.model;
-
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.Table;
-
-@Entity
-@Table(name = "gms_user")
-public class User {
-
-    @Id
-    @Column(length = 50)
-    private String id;
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java
deleted file mode 100644
index 3eebc85..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermission.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package it.inaf.ia2.gms.persistence.model;
-
-import it.inaf.ia2.gms.model.Permission;
-import java.util.Objects;
-import javax.persistence.Column;
-import javax.persistence.Entity;
-import javax.persistence.EnumType;
-import javax.persistence.Enumerated;
-import javax.persistence.Id;
-import javax.persistence.IdClass;
-import javax.persistence.JoinColumn;
-import javax.persistence.ManyToOne;
-import javax.persistence.Table;
-
-/**
- * NOTE: underscores in variable names are used when a field is a reserved JPQL
- * keyword.
- */
-@Entity
-@IdClass(UserGroupPermissionId.class)
-@Table(name = "gms_user_group_permission")
-public class UserGroupPermission {
-
-    @Id
-    @ManyToOne
-    @JoinColumn(name = "user_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
-    private User _user;
-
-    @Id
-    @ManyToOne
-    @JoinColumn(name = "group_id", referencedColumnName = "id", columnDefinition = "varchar(50)")
-    private Group _group;
-
-    @Id
-    @Enumerated(EnumType.STRING)
-    @Column(name = "permission", length = 50)
-    private Permission permission;
-
-    public User getUser() {
-        return _user;
-    }
-
-    public void setUser(User user) {
-        this._user = user;
-    }
-
-    public Group getGroup() {
-        return _group;
-    }
-
-    public void setGroup(Group group) {
-        this._group = group;
-    }
-
-    public Permission getPermission() {
-        return permission;
-    }
-
-    public void setPermission(Permission permission) {
-        this.permission = permission;
-    }
-
-    @Override
-    public int hashCode() {
-        int hash = 7;
-        hash = 47 * hash + Objects.hashCode(this._user);
-        hash = 47 * hash + Objects.hashCode(this._group);
-        hash = 47 * hash + Objects.hashCode(this.permission);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final UserGroupPermission other = (UserGroupPermission) obj;
-        if (!Objects.equals(this._user, other._user)) {
-            return false;
-        }
-        if (!Objects.equals(this._group, other._group)) {
-            return false;
-        }
-        if (this.permission != other.permission) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java
deleted file mode 100644
index c036bfd..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserGroupPermissionId.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package it.inaf.ia2.gms.persistence.model;
-
-import it.inaf.ia2.gms.model.Permission;
-import java.io.Serializable;
-import java.util.Objects;
-
-public class UserGroupPermissionId implements Serializable {
-
-    String _user;
-    String _group;
-    Permission permission;
-
-    @Override
-    public int hashCode() {
-        int hash = 5;
-        hash = 71 * hash + Objects.hashCode(this._user);
-        hash = 71 * hash + Objects.hashCode(this._group);
-        hash = 71 * hash + Objects.hashCode(this.permission);
-        return hash;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        final UserGroupPermissionId other = (UserGroupPermissionId) obj;
-        if (!Objects.equals(this._user, other._user)) {
-            return false;
-        }
-        if (!Objects.equals(this._group, other._group)) {
-            return false;
-        }
-        if (this.permission != other.permission) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelBuilder.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelBuilder.java
new file mode 100644
index 0000000..f47426d
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelBuilder.java
@@ -0,0 +1,62 @@
+package it.inaf.ia2.gms.service;
+
+import it.inaf.ia2.gms.model.GroupsModelRequest;
+import it.inaf.ia2.gms.model.GroupsModelResponse;
+import it.inaf.ia2.gms.model.PaginatedData;
+import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.model.RapUser;
+import it.inaf.ia2.gms.model.UserPermission;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GroupsModelBuilder {
+
+    @Autowired
+    private GroupsService groupsService;
+
+    @Autowired
+    private GroupsTreeBuilder groupsTreeBuilder;
+
+    @Autowired
+    private MembersService membersService;
+
+    @Autowired
+    private PermissionsService permissionsService;
+
+    public GroupsModelResponse getGroupsModel(GroupsModelRequest request, String userId) {
+
+        GroupEntity group = groupsService.getGroupById(request.getGroupId());
+
+        GroupsModelResponse response = new GroupsModelResponse();
+
+        response.setPage("groups");
+
+        response.setBreadcrumbs(groupsService.getBreadcrumbs(group.getPath()));
+
+        Permission currentNodePermissions = permissionsService.getGroupPermission(group, userId);
+        response.setPermission(currentNodePermissions);
+
+        switch (request.getTab()) {
+            case "groups":
+                response.setGroupsPanel(groupsTreeBuilder.listSubGroups(group.getId(), userId, request));
+                break;
+            case "members":
+                if (currentNodePermissions != Permission.TRAVERSE) {
+                    List<RapUser> members = membersService.getMembers(group.getId());
+                    response.setMembersPanel(new PaginatedData<>(members, request.getPaginatorPage(), request.getPaginatorPageSize()));
+                }
+                break;
+            case "permissions":
+                if (currentNodePermissions == Permission.ADMIN) {
+                    List<UserPermission> permissions = permissionsService.getUserPermissions(group);
+                    response.setPermissionsPanel(new PaginatedData<>(permissions, request.getPaginatorPage(), request.getPaginatorPageSize()));
+                }
+                break;
+        }
+
+        return response;
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java
deleted file mode 100644
index 813e171..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsModelService.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package it.inaf.ia2.gms.service;
-
-import it.inaf.ia2.gms.model.GroupBreadcrumb;
-import it.inaf.ia2.gms.model.GroupNode;
-import it.inaf.ia2.gms.model.GroupsModelRequest;
-import it.inaf.ia2.gms.model.GroupsModelResponse;
-import it.inaf.ia2.gms.model.PaginatedData;
-import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.model.RapUser;
-import it.inaf.ia2.gms.model.UserPermission;
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.User;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-@Service
-public class GroupsModelService {
-
-    @Autowired
-    private UsersService usersService;
-
-    @Autowired
-    private GroupsService groupsService;
-
-    @Autowired
-    private MembersService membersService;
-
-    @Autowired
-    private PermissionsService permissionsService;
-
-    public GroupsModelResponse getGroupsModel(GroupsModelRequest request, String userId) {
-
-        User user = usersService.getUserById(userId);
-        Group group = groupsService.getGroupById(request.getGroupId());
-
-        GroupsModelResponse response = new GroupsModelResponse();
-
-        response.setPage("groups");
-
-        response.setBreadcrumbs(buildBreadcrumbs(new ArrayList<>(), group));
-
-        List<Permission> currentNodePermissions = groupsService.getPermissions(group, user);
-        response.setPermissions(currentNodePermissions);
-
-        switch (request.getTab()) {
-            case "groups":
-                List<GroupNode> groupNodes = groupsService.getSubgroups(group, user);
-                response.setGroupsPanel(new PaginatedData<>(groupNodes, request.getPaginatorPage(), request.getPaginatorPageSize()));
-                break;
-            case "members":
-                if (currentNodePermissions.contains(Permission.ADMIN)
-                        || currentNodePermissions.contains(Permission.MANAGE_MEMBERS)
-                        || currentNodePermissions.contains(Permission.VIEW_MEMBERS)) {
-                    List<RapUser> members = membersService.getMembers(group);
-                    response.setMembersPanel(new PaginatedData<>(members, request.getPaginatorPage(), request.getPaginatorPageSize()));
-                }
-                break;
-            case "permissions":
-                if (currentNodePermissions.contains(Permission.ADMIN)) {
-                    List<UserPermission> permissions = permissionsService.getUserPermissions(group);
-                    response.setPermissionsPanel(new PaginatedData<>(permissions, request.getPaginatorPage(), request.getPaginatorPageSize()));
-                }
-                break;
-        }
-
-        return response;
-    }
-
-    private List<GroupBreadcrumb> buildBreadcrumbs(List<GroupBreadcrumb> breadcrumbs, Group group) {
-
-        GroupBreadcrumb bc = new GroupBreadcrumb();
-        bc.setGroupId(group.getId());
-        bc.setGroupName(group.getName());
-        breadcrumbs.add(bc);
-
-        if (group.getParentGroup() == null) {
-            Collections.reverse(breadcrumbs);
-            return breadcrumbs;
-        } else {
-            return buildBreadcrumbs(breadcrumbs, group.getParentGroup());
-        }
-    }
-}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
index c82324a..15ad515 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsService.java
@@ -2,21 +2,13 @@ package it.inaf.ia2.gms.service;
 
 import it.inaf.ia2.gms.exception.BadRequestException;
 import it.inaf.ia2.gms.exception.UnauthorizedException;
-import it.inaf.ia2.gms.persistence.GroupsRepository;
-import it.inaf.ia2.gms.persistence.MembershipRepository;
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
-import java.util.List;
+import it.inaf.ia2.gms.model.GroupBreadcrumb;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import it.inaf.ia2.gms.persistence.PermissionsRepository;
-import it.inaf.ia2.gms.persistence.model.Membership;
-import it.inaf.ia2.gms.model.GroupNode;
 import it.inaf.ia2.gms.model.Permission;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
+import it.inaf.ia2.gms.persistence.GroupsDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import java.util.List;
 import java.util.UUID;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -25,75 +17,82 @@ public class GroupsService {
 
     public static final String ROOT = "ROOT";
 
-    private final GroupsRepository groupsRepository;
-    private final PermissionsRepository permissionsRepository;
-    private final MembershipRepository membershipRepository;
+    private final GroupsDAO groupsDAO;
+    private final PermissionsService permissionsService;
 
     @Autowired
-    public GroupsService(GroupsRepository groupsRepository,
-            PermissionsRepository permissionsRepository,
-            MembershipRepository membershipRepository) {
-        this.groupsRepository = groupsRepository;
-        this.permissionsRepository = permissionsRepository;
-        this.membershipRepository = membershipRepository;
+    public GroupsService(GroupsDAO groupsDAO,
+            PermissionsService permissionsService) {
+        this.groupsDAO = groupsDAO;
+        this.permissionsService = permissionsService;
         createRootIfNecessary();
     }
 
     private void createRootIfNecessary() {
-        if (groupsRepository.count() == 0) {
-            Group root = new Group();
+        if (groupsDAO.count() == 0) {
+            GroupEntity root = new GroupEntity();
             root.setId(ROOT);
             root.setName(ROOT);
-            groupsRepository.save(root);
+            root.setPath("");
+            groupsDAO.createGroup(root);
         }
     }
 
     @Transactional
-    public Group addGroup(String parentId, String groupName, User user) {
+    public GroupEntity addGroup(String parentId, String groupName, String userId) {
 
-        Group parent = getGroupById(parentId);
+        GroupEntity parent = getGroupById(parentId);
 
-        if (!getPermissions(parent, user).contains(Permission.ADMIN)) {
+        if (permissionsService.getGroupPermission(parent, userId) != Permission.ADMIN) {
             throw new UnauthorizedException("Missing admin privileges");
         }
 
-        if (parent.getChildrenGroups().stream()
+        if (groupsDAO.listSubGroups(parent.getPath()).stream()
                 .anyMatch(g -> g.getName().equals(groupName))) {
             throw new BadRequestException("There is already a group named " + groupName);
         }
 
-        Group group = new Group();
-        group.setId(UUID.randomUUID().toString());
+        String newGroupId = UUID.randomUUID().toString();
+
+        String path = parent.getPath();
+        if (!path.isEmpty()) {
+            path += ".";
+        }
+        path += newGroupId;
+
+        GroupEntity group = new GroupEntity();
+        group.setId(newGroupId);
         group.setName(groupName);
-        group.setParentGroup(parent);
-        parent.getChildrenGroups().add(group);
-        group = groupsRepository.save(group);
-        groupsRepository.save(parent);
+        group.setPath(path);
+
+        groupsDAO.createGroup(group);
+
         return group;
     }
 
-    public Group renameGroup(String groupId, String newGroupName, User user) {
+    public GroupEntity renameGroup(String groupId, String newGroupName, String userId) {
 
-        Group group = getGroupById(groupId);
+        GroupEntity group = getGroupById(groupId);
 
-        if (!getPermissions(group, user).contains(Permission.ADMIN)) {
+        if (permissionsService.getGroupPermission(group, userId) != Permission.ADMIN) {
             throw new UnauthorizedException("Missing admin privileges");
         }
 
-        if (group.getParentGroup().getChildrenGroups().stream()
+        if (groupsDAO.listSubGroups(group.getPath()).stream()
                 .anyMatch(g -> g.getName().equals(newGroupName))) {
             throw new BadRequestException("There is already a group named " + newGroupName);
         }
 
         group.setName(newGroupName);
-        return groupsRepository.save(group);
+
+        return groupsDAO.updateGroup(group);
     }
 
-    public Group deleteGroup(String groupId, User user) {
+    public GroupEntity deleteGroup(String groupId, String userId) {
 
-        Group group = getGroupById(groupId);
+        GroupEntity group = getGroupById(groupId);
 
-        if (!getPermissions(group, user).contains(Permission.ADMIN)) {
+        if (permissionsService.getGroupPermission(group, userId) != Permission.ADMIN) {
             throw new UnauthorizedException("Missing admin privileges");
         }
 
@@ -101,130 +100,26 @@ public class GroupsService {
             throw new UnauthorizedException("It is not possible to remove the ROOT");
         }
 
-        Group parent = group.getParentGroup();
-        
-        groupsRepository.delete(group);
-        
-        return parent;
-    }
-
-    @Transactional
-    public List<GroupNode> getSubgroups(Group parent, User user) {
-
-        List<UserGroupPermission> permissions = getAllPermissions(user);
-
-        Map<String, GroupNode> nodesMap = new HashMap<>();
-
-        for (Group childGroup : parent.getChildrenGroups()) {
-            addGroupNodeToMap(childGroup, permissions, nodesMap);
-        }
-
-        List<GroupNode> nodes = new ArrayList<>(nodesMap.values());
-
-        // Sort by group name
-        nodes.sort((n1, n2) -> n1.getGroupName().compareTo(n2.getGroupName()));
-
-        return nodes;
-    }
-
-    public List<Permission> getPermissions(Group group, User user) {
-
-        List<UserGroupPermission> permissions = getAllPermissions(user);
-        Map<String, GroupNode> nodesMap = new HashMap<>();
-
-        addGroupNodeToMap(group, permissions, nodesMap);
-
-        GroupNode groupNode = nodesMap.get(group.getId());
-        if (groupNode == null) {
-            return new ArrayList<>();
-        }
-        return groupNode.getPermissions();
-    }
-
-    /**
-     * Returns all the permissions, including also the calculated ones (the ones
-     * derived from the memberships).
-     */
-    private List<UserGroupPermission> getAllPermissions(User user) {
-
-        // explicit permissions
-        List<UserGroupPermission> permissions = permissionsRepository.findBy_user(user);
-
-        List<UserGroupPermission> implicitPermissions = new ArrayList<>();
-        for (Membership membership : membershipRepository.findBy_user(user)) {
-            UserGroupPermission userGroupPermission = new UserGroupPermission();
-            userGroupPermission.setUser(user);
-            userGroupPermission.setGroup(membership.getGroup());
-            userGroupPermission.setPermission(Permission.TRAVERSE);
-            implicitPermissions.add(userGroupPermission);
-        }
-
-        permissions.addAll(implicitPermissions);
-        return permissions;
-    }
-
-    private void addGroupNodeToMap(Group group, List<UserGroupPermission> permissions, Map<String, GroupNode> nodesMap) {
-
-        for (UserGroupPermission permission : permissions) {
-
-            boolean isChild = false;
-            if (permission.getGroup().getId().equals(group.getId())
-                    || (isChild = isChildOf(permission.getGroup(), group))
-                    || (isParentOf(permission.getGroup(), group))) {
+        String parentPath = group.getParentPath();
+        GroupEntity parent = groupsDAO.findGroupByPath(parentPath)
+                .orElseThrow(() -> new BadRequestException("No group found at path " + parentPath));
 
-                GroupNode node = nodesMap.get(group.getId());
-                if (node == null) {
-                    node = getGroupNode(group);
-                }
+        groupsDAO.deleteGroupById(groupId);
 
-                if (isChild) {
-                    // Traversal only
-                    node.addPermission(Permission.TRAVERSE);
-                } else {
-                    // Direct permission or permission inherited from parent
-                    node.addPermission(permission.getPermission());
-                }
-
-                nodesMap.put(group.getId(), node);
-            }
-        }
-    }
-
-    private GroupNode getGroupNode(Group group) {
-        GroupNode node = new GroupNode();
-        node.setGroupId(group.getId());
-        node.setGroupName(group.getName());
-        node.setHasChildren(!group.getChildrenGroups().isEmpty());
-        return node;
+        return parent;
     }
 
-    private boolean isChildOf(Group group, Group possibleParent) {
-        Group parent = group.getParentGroup();
-        if (parent == null) {
-            // ROOT has no parent
-            return false;
-        }
-        if (possibleParent.getId().equals(parent.getId())) {
-            return true;
-        }
-        // recursive call to parent group
-        return isChildOf(parent, possibleParent);
+    public GroupEntity getGroupById(String groupId) {
+        return groupsDAO.findGroupById(groupId)
+                .orElseThrow(() -> new BadRequestException("Group " + groupId + " not found"));
     }
 
-    private boolean isParentOf(Group group, Group possibleChild) {
-        Group parent = possibleChild.getParentGroup();
-        if (parent == null) {
-            return false;
-        }
-        if (parent.getId().equals(group.getId())) {
-            return true;
-        }
-        // recursive call to parent group
-        return isParentOf(group, parent);
+    public GroupEntity getGroupByPath(String path) {
+        return groupsDAO.findGroupByPath(path)
+                .orElseThrow(() -> new BadRequestException("Group not found at path " + path));
     }
 
-    public Group getGroupById(String groupId) {
-        return groupsRepository.findById(groupId)
-                .orElseThrow(() -> new BadRequestException("Group " + groupId + " not found"));
+    public List<GroupBreadcrumb> getBreadcrumbs(String path) {
+        return groupsDAO.getBreadcrumbs(path);
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
new file mode 100644
index 0000000..f431bf6
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/GroupsTreeBuilder.java
@@ -0,0 +1,82 @@
+package it.inaf.ia2.gms.service;
+
+import it.inaf.ia2.gms.exception.BadRequestException;
+import it.inaf.ia2.gms.model.GroupNode;
+import it.inaf.ia2.gms.model.PaginatedData;
+import it.inaf.ia2.gms.model.PaginatedModelRequest;
+import it.inaf.ia2.gms.persistence.GroupsDAO;
+import it.inaf.ia2.gms.persistence.PermissionsDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GroupsTreeBuilder {
+
+    private final GroupsDAO groupsDAO;
+    private final PermissionsDAO permissionsDAO;
+
+    @Autowired
+    public GroupsTreeBuilder(GroupsDAO groupsDAO, PermissionsDAO permissionsDAO) {
+        this.groupsDAO = groupsDAO;
+        this.permissionsDAO = permissionsDAO;
+    }
+
+    public PaginatedData<GroupNode> listSubGroups(String parentGroupId, String userId, PaginatedModelRequest paginatedModelRequest) {
+
+        GroupEntity parent = groupsDAO.findGroupById(parentGroupId)
+                .orElseThrow(() -> new BadRequestException("Group " + parentGroupId + " not found"));
+
+        // All the sub groups of given parent
+        List<GroupEntity> allGroups = groupsDAO.listSubGroups(parent.getPath());
+
+        // Select only the groups visible to the user
+        List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId);
+        List<GroupNode> userGroups = filterOnPermissions(allGroups, permissions);
+
+        PaginatedData<GroupNode> paginatedGroups = new PaginatedData<>(userGroups,
+                paginatedModelRequest.getPaginatorPage(), paginatedModelRequest.getPaginatorPageSize());
+
+        fillHasChildrenFlags(paginatedGroups.getItems());
+
+        return paginatedGroups;
+    }
+
+    private List<GroupNode> filterOnPermissions(List<GroupEntity> allGroups, List<PermissionEntity> permissions) {
+
+        List<GroupNode> nodes = new ArrayList<>();
+
+        for (GroupEntity group : allGroups) {
+
+            PermissionUtils.getGroupPermission(group, permissions).ifPresent(permission -> {
+
+                GroupNode node = new GroupNode();
+                node.setGroupId(group.getId());
+                node.setGroupName(group.getName());
+                node.setPermission(permission);
+                nodes.add(node);
+            });
+        }
+
+        // Sort by group name
+        nodes.sort((n1, n2) -> n1.getGroupName().compareTo(n2.getGroupName()));
+        return nodes;
+    }
+
+    private void fillHasChildrenFlags(List<GroupNode> groups) {
+
+        Set<String> groupIds = groups.stream().map(g -> g.getGroupId()).collect(Collectors.toSet());
+
+        Map<String, Boolean> hasChildrenMap = groupsDAO.getHasChildrenMap(groupIds);
+
+        for (GroupNode group : groups) {
+            group.setHasChildren(hasChildrenMap.get(group.getGroupId()));
+        }
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java b/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java
index 5e1b5a0..7f9ab1d 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/MembersService.java
@@ -1,9 +1,8 @@
 package it.inaf.ia2.gms.service;
 
 import it.inaf.ia2.gms.model.RapUser;
-import it.inaf.ia2.gms.persistence.MembershipRepository;
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.Membership;
+import it.inaf.ia2.gms.persistence.MembershipsDAO;
+import it.inaf.ia2.gms.persistence.model.MembershipEntity;
 import it.inaf.ia2.gms.rap.RapClient;
 import java.util.List;
 import java.util.Set;
@@ -15,16 +14,17 @@ import org.springframework.stereotype.Service;
 public class MembersService {
 
     @Autowired
-    private MembershipRepository membershipRepository;
+    private MembershipsDAO membershipsDAO;
 
     @Autowired
     private RapClient rapClient;
 
-    public List<RapUser> getMembers(Group group) {
-        List<Membership> memberships = membershipRepository.findBy_group(group);
+    public List<RapUser> getMembers(String groupId) {
+
+        List<MembershipEntity> memberships = membershipsDAO.findByGroup(groupId);
 
         Set<String> userIdentifiers = memberships.stream()
-                .map(m -> m.getUser().getId())
+                .map(m -> m.getUserId())
                 .collect(Collectors.toSet());
 
         return rapClient.getUsers(userIdentifiers);
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionUtils.java b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionUtils.java
new file mode 100644
index 0000000..a4d877a
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionUtils.java
@@ -0,0 +1,40 @@
+package it.inaf.ia2.gms.service;
+
+import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import java.util.List;
+import java.util.Optional;
+
+public class PermissionUtils {
+
+    public static Optional<Permission> getGroupPermission(GroupEntity group, List<PermissionEntity> permissions) {
+
+        Permission groupPermission = null;
+
+        for (PermissionEntity permission : permissions) {
+
+            boolean directPermission = permission.getGroupId().equals(group.getId());
+            boolean permissionInChildGroup = permission.getGroupPath().startsWith(group.getPath());
+            boolean permissionInParentGroup = group.getPath().startsWith(permission.getGroupPath());
+
+            if (directPermission || permissionInChildGroup || permissionInParentGroup) {
+
+                if (permissionInChildGroup) {
+                    // Traversal only
+                    groupPermission = Permission.addPermission(groupPermission, Permission.TRAVERSE);
+                } else {
+                    // Direct permission or permission inherited from parent
+                    groupPermission = Permission.addPermission(groupPermission, permission.getPermission());
+                }
+            }
+
+            if (groupPermission == Permission.ADMIN) {
+                // reached the maximum permission
+                break;
+            }
+        }
+
+        return Optional.ofNullable(groupPermission);
+    }
+}
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java
index 1a8d643..ad1f5a9 100644
--- a/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java
+++ b/gms/src/main/java/it/inaf/ia2/gms/service/PermissionsService.java
@@ -1,12 +1,11 @@
 package it.inaf.ia2.gms.service;
 
-import it.inaf.ia2.gms.exception.BadRequestException;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.RapUser;
 import it.inaf.ia2.gms.model.UserPermission;
-import it.inaf.ia2.gms.persistence.PermissionsRepository;
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import it.inaf.ia2.gms.persistence.PermissionsDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import it.inaf.ia2.gms.rap.RapClient;
 import java.util.ArrayList;
 import java.util.List;
@@ -20,21 +19,21 @@ import org.springframework.stereotype.Service;
 @Service
 public class PermissionsService {
 
-    private final PermissionsRepository permissionsRepository;
+    private final PermissionsDAO permissionsDAO;
     private final RapClient rapClient;
 
     @Autowired
-    public PermissionsService(PermissionsRepository permissionsRepository, RapClient rapClient) {
-        this.permissionsRepository = permissionsRepository;
+    public PermissionsService(PermissionsDAO permissionsDAO, RapClient rapClient) {
+        this.permissionsDAO = permissionsDAO;
         this.rapClient = rapClient;
     }
 
-    public List<UserPermission> getUserPermissions(Group group) {
+    public List<UserPermission> getUserPermissions(GroupEntity group) {
 
-        List<UserGroupPermission> permissions = permissionsRepository.findBy_group(group);
+        List<PermissionEntity> permissions = permissionsDAO.getGroupsPermissions(group.getId());
 
         Set<String> userIdentifiers = permissions.stream()
-                .map(p -> p.getUser().getId())
+                .map(p -> p.getUserId())
                 .collect(Collectors.toSet());
 
         Map<String, RapUser> users = rapClient.getUsers(userIdentifiers).stream()
@@ -42,8 +41,8 @@ public class PermissionsService {
 
         List<UserPermission> result = new ArrayList<>();
 
-        for (UserGroupPermission p : permissions) {
-            RapUser rapUser = users.get(p.getUser().getId());
+        for (PermissionEntity p : permissions) {
+            RapUser rapUser = users.get(p.getUserId());
             if (rapUser != null) {
                 UserPermission permission = new UserPermission();
                 permission.setPermission(p.getPermission());
@@ -55,12 +54,12 @@ public class PermissionsService {
         return result;
     }
 
-    public void deletePermission(String userId, String groupId, Permission permission) {
-
-        UserGroupPermission ugp = permissionsRepository.findPermission(userId, groupId, permission)
-                .orElseThrow(() -> new BadRequestException("Permission not found (UserId="
-                + userId + ", GroupId=" + groupId + ", Permission=" + permission + ")"));
+    public Permission getGroupPermission(GroupEntity group, String userId) {
+        List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId, group.getPath());
+        return PermissionUtils.getGroupPermission(group, permissions).orElse(null);
+    }
 
-        permissionsRepository.delete(ugp);
+    public void deletePermission(String userId, String groupId, Permission permission) {
+        permissionsDAO.deletePermission(userId, groupId, permission);
     }
 }
diff --git a/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java b/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java
deleted file mode 100644
index 3cf6a99..0000000
--- a/gms/src/main/java/it/inaf/ia2/gms/service/UsersService.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package it.inaf.ia2.gms.service;
-
-import it.inaf.ia2.gms.exception.BadRequestException;
-import it.inaf.ia2.gms.persistence.UsersRepository;
-import it.inaf.ia2.gms.persistence.model.User;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-@Service
-public class UsersService {
-
-    private final UsersRepository usersRepository;
-
-    @Autowired
-    public UsersService(UsersRepository usersRepository) {
-        this.usersRepository = usersRepository;
-    }
-
-    public User getUserById(String userId) {
-
-        return usersRepository.findById(userId)
-                .orElseThrow(() -> new BadRequestException("User " + userId + " not found"));
-    }
-}
diff --git a/gms/src/main/resources/application.properties b/gms/src/main/resources/application.properties
index 2f8ac50..c92e337 100644
--- a/gms/src/main/resources/application.properties
+++ b/gms/src/main/resources/application.properties
@@ -9,6 +9,7 @@ security.oauth2.client.scope=openid,email,profile
 security.oauth2.resource.jwk.key-set-uri=http://localhost/rap-ia2/auth/oidc/jwks
 
 logging.level.org.springframework.security=DEBUG
+logging.level.org.springframework.jdbc=TRACE
 
 spring.jpa.hibernate.ddl-auto=update
 spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect
diff --git a/gms/src/main/resources/sql/init.sql b/gms/src/main/resources/sql/init.sql
index 780ce0e..737c319 100644
--- a/gms/src/main/resources/sql/init.sql
+++ b/gms/src/main/resources/sql/init.sql
@@ -8,7 +8,7 @@ CREATE TABLE gms_group (
 );
 
 CREATE INDEX group_path_gist_idx ON gms_group USING GIST(path);
-CREATE INDEX group_path_idx ON gms_group USING btree(path);
+CREATE UNIQUE INDEX group_path_idx ON gms_group USING btree(path);
 CREATE INDEX group_name_idx ON gms_group USING btree(name);
 
 CREATE TABLE gms_membership (
@@ -24,6 +24,8 @@ CREATE TABLE gms_permission (
   group_id varchar NOT NULL,
   user_id varchar NOT NULL,
   permission permission_type NOT NULL,
+  group_path ltree NOT NULL, -- group_path is copied here for performance reasons
   primary key (group_id, user_id, permission),
-  foreign key (group_id) references gms_group(id)
+  foreign key (group_id) references gms_group(id),
+  foreign key (group_path) references gms_group(path)
 );
diff --git a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java
index fc12cfc..8a1ac93 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/controller/GroupsControllerTest.java
@@ -3,7 +3,7 @@ package it.inaf.ia2.gms.controller;
 import it.inaf.ia2.gms.authn.SessionData;
 import it.inaf.ia2.gms.model.GroupsModelRequest;
 import it.inaf.ia2.gms.model.GroupsModelResponse;
-import it.inaf.ia2.gms.service.GroupsModelService;
+import it.inaf.ia2.gms.service.GroupsModelBuilder;
 import static org.junit.Assert.assertEquals;
 import org.junit.Before;
 import org.junit.Test;
@@ -28,7 +28,7 @@ public class GroupsControllerTest {
     private SessionData session;
 
     @Mock
-    private GroupsModelService groupsModelService;
+    private GroupsModelBuilder groupsModelService;
 
     @InjectMocks
     private GroupsController controller;
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupEntityTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupEntityTest.java
new file mode 100644
index 0000000..6e71cef
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupEntityTest.java
@@ -0,0 +1,28 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class GroupEntityTest {
+
+    @Test
+    public void testGetParentPath() {
+
+        GroupEntity root = new GroupEntity();
+        root.setPath("");
+        assertNull(root.getParentPath());
+
+        GroupEntity lbt = new GroupEntity();
+        lbt.setPath("LBT");
+        assertEquals("", lbt.getParentPath());
+
+        GroupEntity p1 = new GroupEntity();
+        p1.setPath("LBT.INAF.P1");
+        assertEquals("LBT.INAF", p1.getParentPath());
+    }
+}
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
index 5309478..643cef6 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
@@ -2,9 +2,11 @@ package it.inaf.ia2.gms.persistence;
 
 import com.google.common.collect.ImmutableSet;
 import io.zonky.test.db.AutoConfigureEmbeddedDatabase;
-import it.inaf.ia2.gms.persistence.model.NewGroup;
+import it.inaf.ia2.gms.model.GroupBreadcrumb;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.UUID;
 import javax.sql.DataSource;
 import static org.junit.Assert.assertEquals;
@@ -36,32 +38,48 @@ public class GroupsDAOTest {
     public void testAll() {
 
         // Create groups
-        NewGroup root = new NewGroup();
+        GroupEntity root = new GroupEntity();
         root.setId("ROOT");
         root.setName("ROOT");
         root.setPath("");
         dao.createGroup(root);
 
-        NewGroup lbt = new NewGroup();
+        GroupEntity lbt = new GroupEntity();
         lbt.setId(getNewGroupId());
         lbt.setName("LBT");
         lbt.setPath(lbt.getId());
         dao.createGroup(lbt);
 
-        NewGroup tng = new NewGroup();
+        GroupEntity tng = new GroupEntity();
         tng.setId(getNewGroupId());
         tng.setName("TNG");
         tng.setPath(tng.getId());
         dao.createGroup(tng);
 
-        NewGroup lbtInaf = new NewGroup();
+        GroupEntity lbtInaf = new GroupEntity();
         lbtInaf.setId(getNewGroupId());
         lbtInaf.setName("INAF");
         lbtInaf.setPath(lbt.getId() + "." + lbtInaf.getId());
         dao.createGroup(lbtInaf);
 
+        // Check count
+        assertEquals(4, dao.count());
+
+        // Find by id
+        Optional<GroupEntity> group = dao.findGroupById(lbtInaf.getId());
+        assertTrue(group.isPresent());
+        assertEquals(lbtInaf, group.get());
+
+        group = dao.findGroupByPath(lbtInaf.getPath());
+        assertTrue(group.isPresent());
+        assertEquals(lbtInaf, group.get());
+
+        // Find unexisting group
+        group = dao.findGroupById("not-found");
+        assertFalse(group.isPresent());
+
         // Sub list
-        List<NewGroup> groups = dao.listSubGroups("");
+        List<GroupEntity> groups = dao.listSubGroups("");
         assertEquals(2, groups.size());
         assertEquals("LBT", groups.get(0).getName());
         assertEquals("TNG", groups.get(1).getName());
@@ -69,17 +87,31 @@ public class GroupsDAOTest {
         groups = dao.listSubGroups(lbt.getId());
         assertEquals(1, groups.size());
         assertEquals("INAF", groups.get(0).getName());
-        
+
         // Children map
         Map<String, Boolean> childrenMap = dao.getHasChildrenMap(ImmutableSet.of(root.getId()));
         assertEquals(1, childrenMap.size());
         assertTrue(childrenMap.get(root.getId()));
-        
+
         childrenMap = dao.getHasChildrenMap(ImmutableSet.of(lbt.getId(), tng.getId()));
         assertEquals(2, childrenMap.size());
         assertTrue(childrenMap.get(lbt.getId()));
         assertFalse(childrenMap.get(tng.getId()));
 
+        // Rename
+        String newName = "renamed";
+        tng.setName(newName);
+        dao.updateGroup(tng);
+        tng = dao.findGroupById(tng.getId()).get();
+        assertEquals(newName, tng.getName());
+
+        // Breadcrumbs
+        List<GroupBreadcrumb> breadcrumbs = dao.getBreadcrumbs(lbt.getId() + "." + lbtInaf.getId());
+        assertEquals(3, breadcrumbs.size());
+        assertEquals(root.getName(), breadcrumbs.get(0).getGroupName());
+        assertEquals(lbt.getName(), breadcrumbs.get(1).getGroupName());
+        assertEquals(lbtInaf.getName(), breadcrumbs.get(2).getGroupName());
+
         // Delete
         dao.deleteGroupById(lbtInaf.getId());
         groups = dao.listSubGroups(lbt.getId());
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
index fdf493d..2c546b0 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/NestedGroupsIntegrationTest.java
@@ -1,12 +1,13 @@
 package it.inaf.ia2.gms.persistence;
 
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.Membership;
-import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
+import io.zonky.test.db.AutoConfigureEmbeddedDatabase;
 import it.inaf.ia2.gms.service.GroupsService;
 import it.inaf.ia2.gms.model.GroupNode;
+import it.inaf.ia2.gms.model.PaginatedModelRequest;
 import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import it.inaf.ia2.gms.service.GroupsTreeBuilder;
 import java.util.List;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -14,144 +15,129 @@ import static org.junit.Assert.assertTrue;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
-@DataJpaTest
-@AutoConfigureTestDatabase
 @RunWith(SpringRunner.class)
+@AutoConfigureEmbeddedDatabase(beanName = "dataSource")
 public class NestedGroupsIntegrationTest {
 
     @Autowired
-    private UsersRepository usersRepository;
+    private PermissionsDAO permissionsDAO;
 
     @Autowired
-    private GroupsRepository groupsRepository;
+    private GroupsService groupsService;
 
     @Autowired
-    private PermissionsRepository permissionsRepository;
-
-    @Autowired
-    private MembershipRepository membershipRepository;
+    private GroupsTreeBuilder groupsTreeBuilder;
 
     @Test
     public void testNestedGroupRetrieval() {
 
-        GroupsService groupsService = new GroupsService(groupsRepository,
-                permissionsRepository, membershipRepository);
-
-        // Create user
-        User user = new User();
-        user.setId("user");
-        user = usersRepository.save(user);
+        String userId = "USER_ID";
 
         // Test super admin
-        UserGroupPermission superAdminPermission = new UserGroupPermission();
-        superAdminPermission.setUser(user);
-        superAdminPermission.setGroup(groupsRepository.findById(GroupsService.ROOT).get());
+        PermissionEntity superAdminPermission = new PermissionEntity();
+        superAdminPermission.setUserId(userId);
+        superAdminPermission.setGroupId(GroupsService.ROOT);
         superAdminPermission.setPermission(Permission.ADMIN);
-        permissionsRepository.save(superAdminPermission);
+        superAdminPermission.setGroupPath("");
+        permissionsDAO.createPermission(superAdminPermission);
 
         // Setup groups
-        Group root = groupsService.getGroupById(GroupsService.ROOT);
-        Group lbt = groupsService.addGroup(GroupsService.ROOT, "LBT", user);
-        Group tng = groupsService.addGroup(GroupsService.ROOT, "TNG", user);
-        Group radio = groupsService.addGroup(GroupsService.ROOT, "Radio", user);
-        Group lbtInaf = groupsService.addGroup(lbt.getId(), "INAF", user);
-        Group lbtInafProgram1 = groupsService.addGroup(lbtInaf.getId(), "P1", user);
-        Group lbtInafProgram2 = groupsService.addGroup(lbtInaf.getId(), "P2", user);
+        GroupEntity root = groupsService.getGroupById(GroupsService.ROOT);
+        GroupEntity lbt = groupsService.addGroup(GroupsService.ROOT, "LBT", userId);
+        GroupEntity tng = groupsService.addGroup(GroupsService.ROOT, "TNG", userId);
+        GroupEntity radio = groupsService.addGroup(GroupsService.ROOT, "Radio", userId);
+        GroupEntity lbtInaf = groupsService.addGroup(lbt.getId(), "INAF", userId);
+        GroupEntity lbtInafProgram1 = groupsService.addGroup(lbtInaf.getId(), "P1", userId);
+        GroupEntity lbtInafProgram2 = groupsService.addGroup(lbtInaf.getId(), "P2", userId);
+
+        PaginatedModelRequest request = new PaginatedModelRequest();
+        request.setPaginatorPage(1);
+        request.setPaginatorPageSize(50);
 
         // Test super admin - level 0 (ROOT)
-        List<GroupNode> groupNodes = groupNodes = groupsService.getSubgroups(root, user);
+        List<GroupNode> groupNodes = groupNodes = groupsTreeBuilder.listSubGroups(root.getId(), userId, request).getItems();
         assertEquals(3, groupNodes.size());
         GroupNode lbtGN = groupNodes.get(0);
         assertEquals("LBT", lbtGN.getGroupName());
-        assertEquals(1, lbtGN.getPermissions().size());
-        assertEquals(Permission.ADMIN, lbtGN.getPermissions().get(0));
+        assertEquals(Permission.ADMIN, lbtGN.getPermission());
         assertTrue(lbtGN.isHasChildren());
         GroupNode radioGN = groupNodes.get(1);
         assertEquals("Radio", radioGN.getGroupName());
-        assertEquals(Permission.ADMIN, radioGN.getPermissions().get(0));
+        assertEquals(Permission.ADMIN, radioGN.getPermission());
         assertFalse(radioGN.isHasChildren());
         GroupNode tngGN = groupNodes.get(2);
         assertEquals("TNG", tngGN.getGroupName());
-        assertEquals(Permission.ADMIN, tngGN.getPermissions().get(0));
+        assertEquals(Permission.ADMIN, tngGN.getPermission());
         assertFalse(tngGN.isHasChildren());
 
         // Test super admin - level 1
-        groupNodes = groupsService.getSubgroups(lbt, user);
+        groupNodes = groupsTreeBuilder.listSubGroups(lbt.getId(), userId, request).getItems();
         assertEquals(1, groupNodes.size());
         GroupNode INAFGN = groupNodes.get(0);
         assertEquals("INAF", INAFGN.getGroupName());
-        assertEquals(Permission.ADMIN, INAFGN.getPermissions().get(0));
+        assertEquals(Permission.ADMIN, INAFGN.getPermission());
         assertTrue(INAFGN.isHasChildren());
 
         // Test super admin - level 2
-        groupNodes = groupsService.getSubgroups(lbtInaf, user);
+        groupNodes = groupsTreeBuilder.listSubGroups(lbtInaf.getId(), userId, request).getItems();
         assertEquals(2, groupNodes.size());
         GroupNode p1 = groupNodes.get(0);
         assertEquals("P1", p1.getGroupName());
-        assertEquals(1, p1.getPermissions().size());
-        assertEquals(Permission.ADMIN, p1.getPermissions().get(0));
+        assertEquals(Permission.ADMIN, p1.getPermission());
         assertFalse(p1.isHasChildren());
         GroupNode p2 = groupNodes.get(1);
         assertEquals("P2", p2.getGroupName());
-        assertEquals(Permission.ADMIN, p2.getPermissions().get(0));
+        assertEquals(Permission.ADMIN, p2.getPermission());
         assertFalse(p2.isHasChildren());
 
         // Setup lower permissions
-        permissionsRepository.delete(superAdminPermission);
+        permissionsDAO.deletePermission(superAdminPermission.getGroupId(),
+                superAdminPermission.getUserId(), superAdminPermission.getPermission());
 
-        UserGroupPermission p1Permission = new UserGroupPermission();
-        p1Permission.setUser(user);
-        p1Permission.setGroup(lbtInafProgram1);
+        PermissionEntity p1Permission = new PermissionEntity();
+        p1Permission.setUserId(userId);
+        p1Permission.setGroupId(lbtInafProgram1.getId());
         p1Permission.setPermission(Permission.MANAGE_MEMBERS);
-        permissionsRepository.save(p1Permission);
+        permissionsDAO.createPermission(p1Permission);
 
-        UserGroupPermission lbtPermission = new UserGroupPermission();
-        lbtPermission.setUser(user);
-        lbtPermission.setGroup(lbtInaf);
+        PermissionEntity lbtPermission = new PermissionEntity();
+        lbtPermission.setUserId(userId);
+        lbtPermission.setGroupId(lbtInaf.getId());
         lbtPermission.setPermission(Permission.VIEW_MEMBERS);
-        permissionsRepository.save(lbtPermission);
-
-        // Setup membership
-        Membership membership = new Membership();
-        membership.setUser(user);
-        membership.setGroup(radio);
-        membershipRepository.save(membership);
+        permissionsDAO.createPermission(lbtPermission);
 
         // Check level 0 (ROOT)
-        groupNodes = groupsService.getSubgroups(root, user);
+        groupNodes = groupsTreeBuilder.listSubGroups(root.getId(), userId, request).getItems();
         assertEquals(2, groupNodes.size());
         lbtGN = groupNodes.get(0);
         assertEquals("LBT", lbtGN.getGroupName());
-        assertEquals(Permission.TRAVERSE, lbtGN.getPermissions().get(0));
+        assertEquals(Permission.TRAVERSE, lbtGN.getPermission());
         assertTrue(lbtGN.isHasChildren());
         radioGN = groupNodes.get(1);
         assertEquals("Radio", radioGN.getGroupName());
-        assertEquals(Permission.TRAVERSE, radioGN.getPermissions().get(0));
+        assertEquals(Permission.TRAVERSE, radioGN.getPermission());
         assertFalse(radioGN.isHasChildren());
 
         // Check level 1
-        groupNodes = groupsService.getSubgroups(lbt, user);
+        groupNodes = groupsTreeBuilder.listSubGroups(lbt.getId(), userId, request).getItems();
         assertEquals(1, groupNodes.size());
         INAFGN = groupNodes.get(0);
         assertEquals("INAF", INAFGN.getGroupName());
-        assertEquals(Permission.VIEW_MEMBERS, INAFGN.getPermissions().get(0));
+        assertEquals(Permission.VIEW_MEMBERS, INAFGN.getPermission());
         assertTrue(INAFGN.isHasChildren());
 
         // Check level 2
-        groupNodes = groupsService.getSubgroups(lbtInaf, user);
+        groupNodes = groupsTreeBuilder.listSubGroups(lbtInaf.getId(), userId, request).getItems();
         assertEquals(2, groupNodes.size());
         p1 = groupNodes.get(0);
         assertEquals("P1", p1.getGroupName());
-        assertEquals(1, p1.getPermissions().size());
-        assertEquals(Permission.MANAGE_MEMBERS, p1.getPermissions().get(0));
+        assertEquals(Permission.MANAGE_MEMBERS, p1.getPermission());
         assertFalse(p1.isHasChildren());
         p2 = groupNodes.get(1);
         assertEquals("P2", p2.getGroupName());
-        assertEquals(Permission.VIEW_MEMBERS, p2.getPermissions().get(0));
+        assertEquals(Permission.VIEW_MEMBERS, p2.getPermission());
         assertFalse(p2.isHasChildren());
     }
 }
diff --git a/gms/src/test/java/it/inaf/ia2/gms/persistence/PermissionsDAOTest.java b/gms/src/test/java/it/inaf/ia2/gms/persistence/PermissionsDAOTest.java
index b5c9a8e..e2bb67d 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/persistence/PermissionsDAOTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/PermissionsDAOTest.java
@@ -2,8 +2,8 @@ package it.inaf.ia2.gms.persistence;
 
 import io.zonky.test.db.AutoConfigureEmbeddedDatabase;
 import it.inaf.ia2.gms.model.Permission;
-import it.inaf.ia2.gms.persistence.model.UserPermission;
-import it.inaf.ia2.gms.persistence.model.NewGroup;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
 import java.util.List;
 import javax.sql.DataSource;
 import static org.junit.Assert.assertEquals;
@@ -35,7 +35,7 @@ public class PermissionsDAOTest {
     @Sql("/sql/init.sql")
     public void testAll() {
 
-        NewGroup root = new NewGroup();
+        GroupEntity root = new GroupEntity();
         root.setId("ROOT");
         root.setName("ROOT");
         root.setPath("");
@@ -43,20 +43,27 @@ public class PermissionsDAOTest {
 
         String userId = "user_id";
 
-        UserPermission permission = new UserPermission();
+        PermissionEntity permission = new PermissionEntity();
         permission.setGroupId(root.getId());
         permission.setUserId(userId);
         permission.setPermission(Permission.ADMIN);
+        permission.setGroupPath(root.getPath());
 
         permissionsDAO.createPermission(permission);
 
-        List<UserPermission> permissions = permissionsDAO.findUserPermissions(userId);
+        List<PermissionEntity> permissions = permissionsDAO.findUserPermissions(userId);
 
         assertEquals(1, permissions.size());
         assertEquals(Permission.ADMIN, permissions.get(0).getPermission());
         assertEquals(userId, permissions.get(0).getUserId());
 
-        permissionsDAO.deletePermission(permission);
+        permissions = permissionsDAO.findUserPermissions(userId, root.getPath());
+        assertEquals(1, permissions.size());
+
+        permissions = permissionsDAO.getGroupsPermissions(root.getId());
+        assertEquals(1, permissions.size());
+
+        permissionsDAO.deletePermission(permission.getGroupId(), permission.getUserId(), permission.getPermission());
 
         permissions = permissionsDAO.findUserPermissions(userId);
         assertTrue(permissions.isEmpty());
diff --git a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java b/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java
index 99e6479..1273d72 100644
--- a/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java
+++ b/gms/src/test/java/it/inaf/ia2/gms/service/PermissionsServiceIntegrationTest.java
@@ -1,14 +1,13 @@
 package it.inaf.ia2.gms.service;
 
+import io.zonky.test.db.AutoConfigureEmbeddedDatabase;
 import it.inaf.ia2.gms.model.Permission;
 import it.inaf.ia2.gms.model.RapUser;
 import it.inaf.ia2.gms.model.UserPermission;
-import it.inaf.ia2.gms.persistence.GroupsRepository;
-import it.inaf.ia2.gms.persistence.PermissionsRepository;
-import it.inaf.ia2.gms.persistence.UsersRepository;
-import it.inaf.ia2.gms.persistence.model.Group;
-import it.inaf.ia2.gms.persistence.model.User;
-import it.inaf.ia2.gms.persistence.model.UserGroupPermission;
+import it.inaf.ia2.gms.persistence.GroupsDAO;
+import it.inaf.ia2.gms.persistence.PermissionsDAO;
+import it.inaf.ia2.gms.persistence.model.GroupEntity;
+import it.inaf.ia2.gms.persistence.model.PermissionEntity;
 import it.inaf.ia2.gms.rap.RapClient;
 import java.util.Collections;
 import java.util.List;
@@ -18,26 +17,20 @@ import org.junit.runner.RunWith;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.when;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.test.context.junit4.SpringRunner;
 
-@DataJpaTest
-@AutoConfigureTestDatabase
 @RunWith(SpringRunner.class)
+@AutoConfigureEmbeddedDatabase(beanName = "dataSource")
 public class PermissionsServiceIntegrationTest {
 
     private static final String USER_ID = "USER_ID";
 
     @Autowired
-    private UsersRepository usersRepository;
+    private GroupsDAO groupsDAO;
 
     @Autowired
-    private GroupsRepository groupsRepository;
-
-    @Autowired
-    private PermissionsRepository permissionsRepository;
+    private PermissionsDAO permissionsDAO;
 
     @MockBean
     private RapClient rapClient;
@@ -50,23 +43,21 @@ public class PermissionsServiceIntegrationTest {
         rapUser.setId(USER_ID);
         when(rapClient.getUsers(any())).thenReturn(Collections.singletonList(rapUser));
 
-        PermissionsService permissionsService = new PermissionsService(permissionsRepository, rapClient);
-
-        // Create user
-        User user = new User();
-        user.setId(USER_ID);
-        user = usersRepository.save(user);
+        PermissionsService permissionsService = new PermissionsService(permissionsDAO, rapClient);
 
-        Group root = new Group();
+        // Create root
+        GroupEntity root = new GroupEntity();
         root.setId(GroupsService.ROOT);
         root.setName(GroupsService.ROOT);
-        root = groupsRepository.save(root);
+        root.setPath("");
+        root = groupsDAO.createGroup(root);
 
-        UserGroupPermission superAdminPermission = new UserGroupPermission();
-        superAdminPermission.setUser(user);
-        superAdminPermission.setGroup(root);
+        PermissionEntity superAdminPermission = new PermissionEntity();
+        superAdminPermission.setUserId(USER_ID);
+        superAdminPermission.setGroupId(root.getId());
         superAdminPermission.setPermission(Permission.ADMIN);
-        permissionsRepository.save(superAdminPermission);
+        superAdminPermission.setGroupPath(root.getPath());
+        permissionsDAO.createPermission(superAdminPermission);
 
         List<UserPermission> permissions = permissionsService.getUserPermissions(root);
 
-- 
GitLab