From d2d3a98b8196a80099c4ff5d8949e6f96e4af91c Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Tue, 6 Aug 2019 16:51:09 +0200
Subject: [PATCH] Created SQL and DAO in order to use Postgres ltree extension

---
 database/Dockerfile                           |   2 +
 gms/pom.xml                                   |   6 ++
 .../inaf/ia2/gms/persistence/GroupsDAO.java   | 101 ++++++++++++++++++
 .../ia2/gms/persistence/PermissionsDAO.java   |  72 +++++++++++++
 .../gms/persistence/model/UserPermission.java |  34 ++++++
 gms/src/main/resources/sql/init.sql           |  29 +++++
 .../ia2/gms/persistence/GroupsDAOTest.java    |  92 ++++++++++++++++
 .../gms/persistence/PermissionsDAOTest.java   |  64 +++++++++++
 8 files changed, 400 insertions(+)
 create mode 100644 database/Dockerfile
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
 create mode 100644 gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserPermission.java
 create mode 100644 gms/src/main/resources/sql/init.sql
 create mode 100644 gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
 create mode 100644 gms/src/test/java/it/inaf/ia2/gms/persistence/PermissionsDAOTest.java

diff --git a/database/Dockerfile b/database/Dockerfile
new file mode 100644
index 0000000..d174c9d
--- /dev/null
+++ b/database/Dockerfile
@@ -0,0 +1,2 @@
+FROM library/postgres:11
+COPY ../gms/src/main/resources/sql/init.sql /docker-entrypoint-initdb.d/ 
diff --git a/gms/pom.xml b/gms/pom.xml
index a24ffb0..905ed51 100644
--- a/gms/pom.xml
+++ b/gms/pom.xml
@@ -63,6 +63,12 @@
             <artifactId>h2</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>io.zonky.test</groupId>
+            <artifactId>embedded-database-spring-test</artifactId>
+            <version>1.5.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
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
new file mode 100644
index 0000000..f1109fa
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/GroupsDAO.java
@@ -0,0 +1,101 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.persistence.model.NewGroup;
+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.Set;
+import java.util.stream.Collectors;
+import javax.sql.DataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class GroupsDAO {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public GroupsDAO(DataSource dataSource) {
+        jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    public NewGroup createGroup(NewGroup group) {
+
+        String sql = "INSERT INTO gms_group (id, name, path) VALUES (?, ?, ?)";
+
+        jdbcTemplate.update(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setString(1, group.getId());
+            ps.setString(2, group.getName());
+            ps.setObject(3, group.getPath(), Types.OTHER);
+            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) {
+
+        String sql = "SELECT id, name, path from gms_group WHERE path ~ ? ORDER BY name";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            ps.setObject(1, getSubGroupsPath(path), Types.OTHER);
+            return ps;
+        }, resultSet -> {
+            List<NewGroup> groups = new ArrayList<>();
+            while (resultSet.next()) {
+                NewGroup group = new NewGroup();
+                group.setId(resultSet.getString("id"));
+                group.setName(resultSet.getString("name"));
+                group.setPath(resultSet.getString("path"));
+                groups.add(group);
+            }
+            return groups;
+        });
+    }
+
+    private String getSubGroupsPath(String path) {
+        if (!path.isEmpty()) {
+            path += ".";
+        }
+        path += "*{1}";
+        return path;
+    }
+
+    public Map<String, Boolean> getHasChildrenMap(Set<String> groupIds) {
+
+        String sql = "SELECT g.id, COUNT(s.*) > 0 AS has_children \n"
+                + "FROM gms_group g\n"
+                + "LEFT JOIN gms_group s ON s.path <@ g.path AND s.path <> g.path\n"
+                + "WHERE g.id IN("
+                + String.join(",", groupIds.stream().map(g -> "?").collect(Collectors.toList()))
+                + ") GROUP BY g.id";
+
+        return jdbcTemplate.query(conn -> {
+            PreparedStatement ps = conn.prepareStatement(sql);
+            int i = 0;
+            for (String groupId : groupIds) {
+                ps.setString(++i, groupId);
+            }
+
+            return ps;
+        }, resultSet -> {
+            Map<String, Boolean> map = new HashMap<>();
+            while (resultSet.next()) {
+                map.put(resultSet.getString("id"), resultSet.getBoolean("has_children"));
+            }
+            return map;
+        });
+    }
+}
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
new file mode 100644
index 0000000..0c48022
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/PermissionsDAO.java
@@ -0,0 +1,72 @@
+package it.inaf.ia2.gms.persistence;
+
+import it.inaf.ia2.gms.model.Permission;
+import it.inaf.ia2.gms.persistence.model.UserPermission;
+import java.sql.PreparedStatement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+import javax.sql.DataSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PermissionsDAO {
+
+    private final JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    public PermissionsDAO(DataSource dataSource) {
+        jdbcTemplate = new JdbcTemplate(dataSource);
+    }
+
+    public UserPermission createPermission(UserPermission userPermission) {
+
+        String sql = "INSERT INTO gms_permission(group_id, user_id, permission) 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);
+            return ps;
+        });
+
+        return userPermission;
+    }
+
+    public List<UserPermission> findUserPermissions(String userId) {
+
+        String sql = "SELECT group_id, permission 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<>();
+            while (resultSet.next()) {
+                UserPermission permission = new UserPermission();
+                permission.setGroupId(resultSet.getString("group_id"));
+                permission.setUserId(userId);
+                permission.setPermission(Permission.valueOf(resultSet.getString("permission")));
+                permissions.add(permission);
+            }
+            return permissions;
+        });
+    }
+
+    public void deletePermission(UserPermission userPermission) {
+
+        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);
+            return ps;
+        });
+    }
+}
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/UserPermission.java
new file mode 100644
index 0000000..6d1e839
--- /dev/null
+++ b/gms/src/main/java/it/inaf/ia2/gms/persistence/model/UserPermission.java
@@ -0,0 +1,34 @@
+package it.inaf.ia2.gms.persistence.model;
+
+import it.inaf.ia2.gms.model.Permission;
+
+public class UserPermission {
+
+    private String userId;
+    private String groupId;
+    private Permission permission;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public void setPermission(Permission permission) {
+        this.permission = permission;
+    }
+}
diff --git a/gms/src/main/resources/sql/init.sql b/gms/src/main/resources/sql/init.sql
new file mode 100644
index 0000000..780ce0e
--- /dev/null
+++ b/gms/src/main/resources/sql/init.sql
@@ -0,0 +1,29 @@
+CREATE EXTENSION IF NOT EXISTS ltree;
+
+CREATE TABLE gms_group (
+  id varchar NOT NULL,
+  name text NOT NULL,
+  path ltree NOT NULL,
+  primary key(id)
+);
+
+CREATE INDEX group_path_gist_idx ON gms_group USING GIST(path);
+CREATE 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 (
+  group_id varchar NOT NULL,
+  user_id varchar NOT NULL,
+  primary key (group_id, user_id),
+  foreign key (group_id) references gms_group(id)
+);
+
+CREATE TYPE permission_type AS ENUM ('VIEW_MEMBERS', 'MANAGE_MEMBERS', 'ADMIN');
+
+CREATE TABLE gms_permission (
+  group_id varchar NOT NULL,
+  user_id varchar NOT NULL,
+  permission permission_type NOT NULL,
+  primary key (group_id, user_id, permission),
+  foreign key (group_id) references gms_group(id)
+);
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
new file mode 100644
index 0000000..5309478
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/GroupsDAOTest.java
@@ -0,0 +1,92 @@
+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 java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import javax.sql.DataSource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@AutoConfigureEmbeddedDatabase(beanName = "dataSource")
+public class GroupsDAOTest {
+
+    @Autowired
+    private DataSource dataSource;
+
+    private GroupsDAO dao;
+
+    @Before
+    public void setUp() {
+        dao = new GroupsDAO(dataSource);
+    }
+
+    @Test
+    @Sql("/sql/init.sql")
+    public void testAll() {
+
+        // Create groups
+        NewGroup root = new NewGroup();
+        root.setId("ROOT");
+        root.setName("ROOT");
+        root.setPath("");
+        dao.createGroup(root);
+
+        NewGroup lbt = new NewGroup();
+        lbt.setId(getNewGroupId());
+        lbt.setName("LBT");
+        lbt.setPath(lbt.getId());
+        dao.createGroup(lbt);
+
+        NewGroup tng = new NewGroup();
+        tng.setId(getNewGroupId());
+        tng.setName("TNG");
+        tng.setPath(tng.getId());
+        dao.createGroup(tng);
+
+        NewGroup lbtInaf = new NewGroup();
+        lbtInaf.setId(getNewGroupId());
+        lbtInaf.setName("INAF");
+        lbtInaf.setPath(lbt.getId() + "." + lbtInaf.getId());
+        dao.createGroup(lbtInaf);
+
+        // Sub list
+        List<NewGroup> groups = dao.listSubGroups("");
+        assertEquals(2, groups.size());
+        assertEquals("LBT", groups.get(0).getName());
+        assertEquals("TNG", groups.get(1).getName());
+
+        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()));
+
+        // Delete
+        dao.deleteGroupById(lbtInaf.getId());
+        groups = dao.listSubGroups(lbt.getId());
+        assertTrue(groups.isEmpty());
+    }
+
+    private String getNewGroupId() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+}
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
new file mode 100644
index 0000000..b5c9a8e
--- /dev/null
+++ b/gms/src/test/java/it/inaf/ia2/gms/persistence/PermissionsDAOTest.java
@@ -0,0 +1,64 @@
+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 java.util.List;
+import javax.sql.DataSource;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.jdbc.Sql;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@AutoConfigureEmbeddedDatabase(beanName = "dataSource")
+public class PermissionsDAOTest {
+
+    @Autowired
+    private DataSource dataSource;
+
+    private GroupsDAO groupsDAO;
+    private PermissionsDAO permissionsDAO;
+
+    @Before
+    public void setUp() {
+        groupsDAO = new GroupsDAO(dataSource);
+        permissionsDAO = new PermissionsDAO(dataSource);
+    }
+
+    @Test
+    @Sql("/sql/init.sql")
+    public void testAll() {
+
+        NewGroup root = new NewGroup();
+        root.setId("ROOT");
+        root.setName("ROOT");
+        root.setPath("");
+        groupsDAO.createGroup(root);
+
+        String userId = "user_id";
+
+        UserPermission permission = new UserPermission();
+        permission.setGroupId(root.getId());
+        permission.setUserId(userId);
+        permission.setPermission(Permission.ADMIN);
+
+        permissionsDAO.createPermission(permission);
+
+        List<UserPermission> 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);
+        assertTrue(permissions.isEmpty());
+    }
+}
-- 
GitLab