Add web-service: delete role by query and group.

Change-Id: I3227b1162c44b755cf07c13373bbfe1eeb88d58e
diff --git a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
index 4fcba47..8ec7c89 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
@@ -9,9 +9,11 @@
 
 import de.ids_mannheim.korap.constant.PredefinedRole;
 import de.ids_mannheim.korap.constant.PrivilegeType;
+import de.ids_mannheim.korap.entity.QueryDO;
 import de.ids_mannheim.korap.entity.QueryDO_;
 import de.ids_mannheim.korap.entity.Role;
 import de.ids_mannheim.korap.entity.Role_;
+import de.ids_mannheim.korap.entity.UserGroup;
 import de.ids_mannheim.korap.entity.UserGroupMember;
 import de.ids_mannheim.korap.entity.UserGroupMember_;
 import de.ids_mannheim.korap.entity.UserGroup_;
@@ -30,6 +32,7 @@
 import jakarta.persistence.criteria.JoinType;
 import jakarta.persistence.criteria.ListJoin;
 import jakarta.persistence.criteria.Root;
+import jakarta.persistence.criteria.Subquery;
 
 /**
  * Manages database queries and transactions regarding {@link Role}
@@ -162,25 +165,6 @@
         return new HashSet<Role>(resultList);
     }
 
-    public void deleteRole (int roleId) throws KustvaktException {
-
-        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
-        CriteriaDelete<Role> delete = cb.createCriteriaDelete(Role.class);
-        Root<Role> role = delete.from(Role.class);
-
-        delete.where(
-                cb.equal(role.get("id"), roleId));
-        
-        try {
-            entityManager.createQuery(delete).executeUpdate();
-        }
-        catch (NoResultException e) {
-            throw new KustvaktException(StatusCodes.NO_RESOURCE_FOUND,
-                    "Role is not found", String.valueOf(roleId));
-        }
-        
-    }
-
     public Role retrieveRoleByGroupIdQueryIdPrivilege (int groupId, int queryId,
             PrivilegeType p) throws KustvaktException {
 
@@ -192,12 +176,51 @@
         role.fetch(Role_.query, JoinType.INNER);
 
         query.select(role);
-        query.where(cb.equal(role.get(Role_.query).get(QueryDO_.id), queryId),
-                cb.equal(role.get(Role_.privilege), p), cb.equal(
-                        role.get(Role_.userGroup).get(UserGroup_.id), groupId));
+        query.where(
+                cb.equal(role.get(Role_.query).get(QueryDO_.id), queryId),
+                cb.equal(role.get(Role_.privilege), p), 
+                cb.equal(role.get(Role_.userGroup).get(UserGroup_.id),
+                        groupId));
 
         TypedQuery<Role> q = entityManager.createQuery(query);
         return (Role) q.getSingleResult();
     }
 
+    @Deprecated
+    public void deleteRole (int roleId) throws KustvaktException {
+
+        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+        CriteriaDelete<Role> delete = cb.createCriteriaDelete(Role.class);
+        Root<Role> role = delete.from(Role.class);
+
+        delete.where(
+                cb.equal(role.get("id"), roleId));
+        
+        entityManager.createQuery(delete).executeUpdate();
+    }
+    
+    public void deleteRoleByGroupAndQuery (String groupName,
+            String queryCreator, String queryName) throws KustvaktException {
+        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+        
+        CriteriaDelete<Role> delete = cb.createCriteriaDelete(Role.class);
+        Root<Role> deleteRole = delete.from(Role.class);
+        
+        Subquery<Integer> subquery = delete.subquery(Integer.class);
+        Root<Role> role = subquery.from(Role.class);
+        Join<Role, UserGroup> groupRole = role.join(Role_.userGroup);
+        Join<Role, QueryDO> queryRole = role.join(Role_.query);
+
+        subquery.select(role.get(Role_.id))
+                .where(cb.and(
+                        cb.equal(groupRole.get(UserGroup_.name), groupName),
+                        cb.equal(queryRole.get(QueryDO_.createdBy),
+                                queryCreator),
+                        cb.equal(queryRole.get(QueryDO_.name), queryName)));
+
+       
+        delete.where(deleteRole.get(Role_.id).in(subquery));
+        entityManager.createQuery(delete).executeUpdate();
+    }
+
 }
diff --git a/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java b/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
index 85b35e8..f8b0474 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
@@ -374,30 +374,4 @@
         }
 
     }
-
-    public void deleteQueryFromGroup (int queryId, int groupId)
-            throws KustvaktException {
-        ParameterChecker.checkIntegerValue(queryId, "queryId");
-        ParameterChecker.checkIntegerValue(groupId, "groupId");
-
-        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
-        CriteriaQuery<QueryAccess> criteriaQuery = criteriaBuilder
-                .createQuery(QueryAccess.class);
-
-        Root<QueryAccess> root = criteriaQuery.from(QueryAccess.class);
-        Join<QueryAccess, QueryDO> queryAccess = root.join(QueryAccess_.query);
-        Join<QueryAccess, UserGroup> group = root.join(QueryAccess_.userGroup);
-
-        Predicate query = criteriaBuilder.equal(queryAccess.get(QueryDO_.id),
-                queryId);
-        Predicate userGroup = criteriaBuilder.equal(group.get(UserGroup_.id),
-                groupId);
-
-        criteriaQuery.select(root);
-        criteriaQuery.where(criteriaBuilder.and(query, userGroup));
-        Query q = entityManager.createQuery(criteriaQuery);
-        QueryAccess access = (QueryAccess) q.getSingleResult();
-        entityManager.remove(access);
-    }
-
 }
diff --git a/src/main/java/de/ids_mannheim/korap/service/QueryService.java b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
index eba6a28..42fd7cb 100644
--- a/src/main/java/de/ids_mannheim/korap/service/QueryService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
@@ -581,7 +581,7 @@
         return accessConverter.createQueryAccessDto(accessList);
     }
 
-    public List<QueryAccessDto> listQueryAccessByGroup (String username,
+    public List<QueryAccessDto> listRolesByGroup (String username,
             String groupName) throws KustvaktException {
         UserGroup userGroup = userGroupService
                 .retrieveUserGroupByName(groupName);
@@ -600,7 +600,8 @@
         return accessConverter.createRoleDto(roles);
     }
 
-    public void deleteQueryAccess (int roleId, String username)
+    @Deprecated
+    public void deleteRoleById (int roleId, String username)
             throws KustvaktException {
 
         Role role = roleDao.retrieveRoleById(roleId);
@@ -615,6 +616,23 @@
         }
 
     }
+    
+    public void deleteRoleByGroupAndQuery (String groupName,
+            String queryCreator, String queryName, String deleteBy)
+            throws KustvaktException {
+        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName,
+                false);
+        if (userGroupService.isUserGroupAdmin(deleteBy, userGroup)
+                || adminDao.isAdmin(deleteBy)) {
+            roleDao.deleteRoleByGroupAndQuery(groupName, queryCreator,
+                    queryName);
+        }
+        else {
+            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                    "Unauthorized operation for user: " + deleteBy, deleteBy);
+        }
+
+    }
 
     public JsonNode retrieveKoralQuery (String username, String queryName,
             String createdBy, QueryType queryType) throws KustvaktException {
diff --git a/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java b/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
index d7f937c..9ba7efa 100644
--- a/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
+++ b/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
@@ -346,6 +346,36 @@
         }
         return Response.ok("SUCCESS").build();
     }
+    
+    /**
+     * Delete all roles for a given group name and vc. Only Group and
+     * system admin are eligible.
+     * 
+     * @param securityContext
+     * @param vcCreator
+     * @param vcName
+     * @param groupName
+     * @return HTTP status 200, if successful
+     */
+    @DELETE
+    @Path("~{vcCreator}/{vcName}/delete/@{groupName}")
+    public Response deleteRoleByGroupAndQuery (
+            @Context SecurityContext securityContext,
+            @PathParam("vcCreator") String vcCreator,
+            @PathParam("vcName") String vcName,
+            @PathParam("groupName") String groupName) {
+        TokenContext context = (TokenContext) securityContext
+                .getUserPrincipal();
+        try {
+            scopeService.verifyScope(context, OAuth2Scope.DELETE_VC_ACCESS);
+            service.deleteRoleByGroupAndQuery(groupName, vcCreator, vcName,
+                    context.getUsername());
+        }
+        catch (KustvaktException e) {
+            throw kustvaktResponseHandler.throwit(e);
+        }
+        return Response.ok().build();
+    }
 
     /**
      * Only VCA Admins and system admins are allowed to delete a
@@ -358,6 +388,7 @@
      * @param accessId
      * @return
      */
+    @Deprecated
     @DELETE
     @Path("access/{accessId}")
     public Response deleteAccessById (
@@ -367,13 +398,16 @@
                 .getUserPrincipal();
         try {
             scopeService.verifyScope(context, OAuth2Scope.DELETE_VC_ACCESS);
-            service.deleteQueryAccess(accessId, context.getUsername());
+            service.deleteRoleById(accessId, context.getUsername());
         }
         catch (KustvaktException e) {
             throw kustvaktResponseHandler.throwit(e);
         }
         return Response.ok().build();
     }
+    
+
+    
 
     /**
      * Lists active VC-accesses available to user.
@@ -386,7 +420,7 @@
      */
     @GET
     @Path("access")
-    public List<QueryAccessDto> listAccess (
+    public List<QueryAccessDto> listRoles (
             @Context SecurityContext securityContext,
             @QueryParam("groupName") String groupName) {
         TokenContext context = (TokenContext) securityContext
@@ -394,7 +428,7 @@
         try {
             scopeService.verifyScope(context, OAuth2Scope.VC_ACCESS_INFO);
             if (groupName != null && !groupName.isEmpty()) {
-                return service.listQueryAccessByGroup(context.getUsername(),
+                return service.listRolesByGroup(context.getUsername(),
                         groupName);
             }
             else {
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java
index c4a43f7..442afdc 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusSharingTest.java
@@ -60,7 +60,7 @@
     }
 
     @Test
-    public void testSharePrivateVCByGroupAdmin ()
+    public void testShareVC_ByGroupAdmin ()
             throws ProcessingException, KustvaktException {
         createMarlinGroup();
         inviteMember(marlinGroupName, "marlin", "nemo");
@@ -69,11 +69,11 @@
         JsonNode node = listAccessByGroup("marlin", marlinGroupName);
         assertEquals(0, node.size());
         
+        // share by member unauthorized
         Response response = shareVCByCreator("nemo", "nemo-vc",
                 marlinGroupName);
         testResponseUnauthorized(response, "nemo");
         
-        
         Form form = new Form();
         form.param("memberUsername", "nemo");
         form.param("role", PredefinedRole.GROUP_ADMIN.name());
@@ -83,11 +83,42 @@
         
         node = listAccessByGroup("marlin", marlinGroupName);
         assertEquals(1, node.size());
-//        System.out.println(node.toPrettyString());
         deleteGroupByName(marlinGroupName, "marlin");
     }
 
     @Test
+    public void testSharePrivateVC () throws KustvaktException {
+        String vcName = "new_private_vc";
+        createPrivateVC(testUser, vcName);
+        
+        String groupName = "DNB-group";
+        Response response = createUserGroup(groupName, "DNB users", testUser);
+        assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+        listUserGroup(testUser, groupName);
+        String memberName = "darla";
+        testInviteMember(groupName, testUser, memberName);
+        subscribeToGroup(memberName, groupName);
+        
+        JsonNode node = listAccessByGroup(testUser, groupName);
+        assertEquals(0, node.size());
+        
+        // share vc to group
+        shareVCByCreator(testUser, vcName, groupName);
+        
+        // check member roles
+        node = listAccessByGroup(testUser, groupName);
+        assertEquals(1, node.size());
+        
+        deleteRoleByGroupAndQuery(testUser, vcName, groupName, testUser);
+        
+        node = listAccessByGroup(testUser, groupName);
+        assertEquals(0, node.size());
+        
+        deleteVC(vcName, testUser, testUser);
+        deleteGroupByName(groupName, testUser);
+    }
+    
+    @Test
     public void testShareProjectVC () throws KustvaktException {
         String vcName = "new_project_vc";
         createProjectVC(testUser, vcName);
@@ -111,7 +142,7 @@
         response = createUserGroup(groupName, "Owid users", testUser);
         assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
         listUserGroup(testUser, groupName);
-        testInviteMember(groupName, testUser, "darla");
+        testInviteMember(groupName, testUser, memberName);
         subscribeToGroup(memberName, groupName);
         checkMemberInGroup(memberName, testUser, groupName);
         
@@ -138,6 +169,50 @@
         node = JsonUtils.readTree(response.readEntity(String.class));
         assertEquals(StatusCodes.NO_RESOURCE_FOUND,
                 node.at("/errors/0/0").asInt());
+        
+        deleteGroupByName(groupName, testUser);
+    }
+    
+    @Test
+    public void testShareMultipleVC () throws KustvaktException {
+        String vc1 = "new_private_vc";
+        String vc2 = "new_project_vc";
+        createPrivateVC(testUser, vc1);
+        createProjectVC(testUser, vc2);
+        
+        String groupName = "DNB-group";
+        Response response = createUserGroup(groupName, "DNB users", testUser);
+        assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
+        listUserGroup(testUser, groupName);
+        String memberName = "darla";
+        testInviteMember(groupName, testUser, memberName);
+        subscribeToGroup(memberName, groupName);
+        
+        shareVC(testUser, vc1, groupName, testUser);
+        shareVC(testUser, vc2, groupName, testUser);
+        
+        // list user VC
+        JsonNode node = listVC(testUser);
+        assertEquals(3, node.size());
+        
+        node = listVC(memberName);
+        assertEquals(3, node.size());
+        
+        deleteRoleByGroupAndQuery(testUser, vc1, groupName, testUser);
+        
+        node = listVC(memberName);
+        assertEquals(2, node.size());
+        
+        node = listVC(testUser);
+        assertEquals(3, node.size());
+        
+        deleteVC(vc1, testUser, testUser);
+        deleteVC(vc2, testUser, testUser);
+        
+        node = listVC(testUser);
+        assertEquals(1, node.size());
+        
+        deleteGroupByName(groupName, testUser);
     }
 
     private JsonNode listUserGroup (String username, String groupName)
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java
index 6972e16..cacca06 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusTestBase.java
@@ -246,6 +246,18 @@
         return response;
     }
     
+    protected Response deleteRoleByGroupAndQuery (String vcCreator, String vcName,
+            String groupName, String deleteBy)
+            throws ProcessingException, KustvaktException {
+        Response response = target().path(API_VERSION).path("vc")
+                .path("~" + vcCreator).path(vcName).path("delete")
+                .path("@" + groupName).request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue(deleteBy, "pass"))
+                .delete();
+        return response;
+    }
+    
     protected Response searchWithVCRef (String username, String vcCreator,
             String vcName) throws KustvaktException {
         Response response = target().path(API_VERSION).path("search")