Update auto-hidden group mechanism.

Change-Id: I4794f7bf75fc102367fbba39941194267e05276e
diff --git a/src/main/java/de/ids_mannheim/korap/dao/QueryAccessDao.java b/src/main/java/de/ids_mannheim/korap/dao/QueryAccessDao.java
index 1d5ce75..10bf684 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/QueryAccessDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/QueryAccessDao.java
@@ -243,6 +243,7 @@
         }
     }
 
+    @Deprecated
     public void createAccessToQuery (QueryDO query, UserGroup userGroup)
             throws KustvaktException {
     
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 42d2021..72fdb69 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
@@ -8,6 +8,7 @@
 import org.springframework.transaction.annotation.Transactional;
 
 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.Role;
 import de.ids_mannheim.korap.entity.Role_;
@@ -190,4 +191,22 @@
         
     }
 
+    public Role retrieveRoleByPrivilegeAndQuery (PrivilegeType p,
+            int queryId) throws KustvaktException {
+
+        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+        CriteriaQuery<Role> query = cb.createQuery(Role.class);
+
+        Root<Role> role = query.from(Role.class);
+        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));
+
+        TypedQuery<Role> q = entityManager.createQuery(query);
+        return (Role) q.getSingleResult();
+    }
+
 }
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 afd6086..a79e13f 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
@@ -5,6 +5,27 @@
 import java.util.List;
 import java.util.Set;
 
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.constant.PrivilegeType;
+import de.ids_mannheim.korap.constant.QueryAccessStatus;
+import de.ids_mannheim.korap.constant.UserGroupStatus;
+import de.ids_mannheim.korap.entity.QueryAccess;
+import de.ids_mannheim.korap.entity.QueryAccess_;
+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_;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.utils.ParameterChecker;
 import jakarta.persistence.EntityManager;
 import jakarta.persistence.NoResultException;
 import jakarta.persistence.PersistenceContext;
@@ -16,28 +37,6 @@
 import jakarta.persistence.criteria.Predicate;
 import jakarta.persistence.criteria.Root;
 
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Repository;
-import org.springframework.transaction.annotation.Transactional;
-
-import de.ids_mannheim.korap.constant.GroupMemberStatus;
-import de.ids_mannheim.korap.constant.PredefinedRole;
-import de.ids_mannheim.korap.constant.PrivilegeType;
-import de.ids_mannheim.korap.constant.UserGroupStatus;
-import de.ids_mannheim.korap.constant.QueryAccessStatus;
-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_;
-import de.ids_mannheim.korap.entity.QueryDO;
-import de.ids_mannheim.korap.entity.QueryAccess;
-import de.ids_mannheim.korap.entity.QueryAccess_;
-import de.ids_mannheim.korap.entity.QueryDO_;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
-import de.ids_mannheim.korap.utils.ParameterChecker;
-
 /**
  * Manages database queries and transactions regarding
  * {@link UserGroup} entity and database table.
@@ -69,20 +68,22 @@
         entityManager.persist(group);
         entityManager.flush();
         
-        Set<Role> roles = createUserGroupAdminRoles(group);
-        for (Role role : roles) {
-            entityManager.persist(role);
-        }
-        entityManager.flush();
+        if (createdBy != "system") {
+            Set<Role> roles = createUserGroupAdminRoles(group);
+            for (Role role : roles) {
+                entityManager.persist(role);
+            }
+            entityManager.flush();
         
-        UserGroupMember owner = new UserGroupMember();
-        owner.setUserId(createdBy);
-        owner.setCreatedBy(createdBy);
-        owner.setStatus(GroupMemberStatus.ACTIVE);
-        owner.setGroup(group);
-        owner.setRoles(roles);
-        entityManager.persist(owner);
-        entityManager.flush();
+            UserGroupMember owner = new UserGroupMember();
+            owner.setUserId(createdBy);
+            owner.setCreatedBy(createdBy);
+            owner.setStatus(GroupMemberStatus.ACTIVE);
+            owner.setGroup(group);
+            owner.setRoles(roles);
+            entityManager.persist(owner);
+            entityManager.flush();
+        };
         
         return group.getId();
     }
@@ -249,23 +250,24 @@
                     "Group " + groupName + " is not found", groupName, e);
         }
     }
-
-    public UserGroup retrieveHiddenGroupByQuery (int queryId)
+    
+    public UserGroup retrieveHiddenGroupByQueryName (String queryName)
             throws KustvaktException {
-        ParameterChecker.checkIntegerValue(queryId, "queryId");
+        ParameterChecker.checkNameValue(queryName, "queryName");
 
         CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
         CriteriaQuery<UserGroup> criteriaQuery = criteriaBuilder
                 .createQuery(UserGroup.class);
 
         Root<UserGroup> root = criteriaQuery.from(UserGroup.class);
-        Join<UserGroup, QueryAccess> access = root.join(UserGroup_.queryAccess);
-        Join<QueryAccess, QueryDO> query = access.join(QueryAccess_.query);
+        Join<UserGroup, Role> group_role = root.join(UserGroup_.roles);
+        Join<Role, QueryDO> query_role = group_role.join(Role_.query);
 
         Predicate p = criteriaBuilder.and(
                 criteriaBuilder.equal(root.get(UserGroup_.status),
                         UserGroupStatus.HIDDEN),
-                criteriaBuilder.equal(query.get(QueryDO_.id), queryId));
+                criteriaBuilder.equal(query_role.get(QueryDO_.name), queryName)
+        );
 
         criteriaQuery.select(root);
         criteriaQuery.where(p);
@@ -275,7 +277,40 @@
             return (UserGroup) q.getSingleResult();
         }
         catch (NoResultException e) {
-            throw new KustvaktException(StatusCodes.NO_RESULT_FOUND,
+            throw new KustvaktException(StatusCodes.NO_RESOURCE_FOUND,
+                    "No hidden group for query " + queryName
+                            + " is found",
+                    String.valueOf(queryName), e);
+        }
+
+    }
+
+    public UserGroup retrieveHiddenGroupByQueryId (int queryId)
+            throws KustvaktException {
+        ParameterChecker.checkIntegerValue(queryId, "queryId");
+
+        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
+        CriteriaQuery<UserGroup> criteriaQuery = criteriaBuilder
+                .createQuery(UserGroup.class);
+
+        Root<UserGroup> root = criteriaQuery.from(UserGroup.class);
+        Join<UserGroup, Role> group_role = root.join(UserGroup_.roles);
+        Join<Role, QueryDO> query_role = group_role.join(Role_.query);
+
+        Predicate p = criteriaBuilder.and(
+                criteriaBuilder.equal(root.get(UserGroup_.status),
+                        UserGroupStatus.HIDDEN),
+                criteriaBuilder.equal(query_role.get(QueryDO_.id), queryId));
+
+        criteriaQuery.select(root);
+        criteriaQuery.where(p);
+        Query q = entityManager.createQuery(criteriaQuery);
+
+        try {
+            return (UserGroup) q.getSingleResult();
+        }
+        catch (NoResultException e) {
+            throw new KustvaktException(StatusCodes.NO_RESOURCE_FOUND,
                     "No hidden group for query with id " + queryId
                             + " is found",
                     String.valueOf(queryId), e);
diff --git a/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java b/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
index 1bf7fb9..d7b57bd 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
@@ -45,6 +45,7 @@
     public void addMember (UserGroupMember member) throws KustvaktException {
         ParameterChecker.checkObjectValue(member, "userGroupMember");
         entityManager.persist(member);
+        entityManager.flush();
     }
 
     public void updateMember (UserGroupMember member) throws KustvaktException {
diff --git a/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java b/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java
index c43ddc4..bc09718 100644
--- a/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java
+++ b/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java
@@ -30,6 +30,8 @@
     @JsonInclude(JsonInclude.Include.NON_EMPTY)
     private List<UserGroupMemberDto> members;
 
+    @JsonInclude(JsonInclude.Include.NON_NULL)
     private GroupMemberStatus userMemberStatus;
+    @JsonInclude(JsonInclude.Include.NON_NULL)
     private List<PredefinedRole> userRoles;
 }
diff --git a/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java b/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
index 4508165..4bb17fe 100644
--- a/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
+++ b/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
@@ -57,8 +57,9 @@
             cascade = CascadeType.REMOVE)
     private List<UserGroupMember> members;
 
-    @OneToMany(mappedBy = "userGroup", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
-    private List<QueryAccess> queryAccess;
+    @OneToMany(mappedBy = "userGroup", fetch = FetchType.LAZY, 
+            cascade = CascadeType.REMOVE)
+    private List<Role> roles;
 
     @Override
     public String toString () {
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 212da68..3a9483f 100644
--- a/src/main/java/de/ids_mannheim/korap/service/QueryService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
@@ -3,6 +3,7 @@
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -27,6 +28,7 @@
 import de.ids_mannheim.korap.dao.QueryAccessDao;
 import de.ids_mannheim.korap.dao.QueryDao;
 import de.ids_mannheim.korap.dao.RoleDao;
+import de.ids_mannheim.korap.dao.UserGroupDao;
 import de.ids_mannheim.korap.dao.UserGroupMemberDao;
 import de.ids_mannheim.korap.dto.QueryAccessDto;
 import de.ids_mannheim.korap.dto.QueryDto;
@@ -48,6 +50,7 @@
 import de.ids_mannheim.korap.web.controller.QueryReferenceController;
 import de.ids_mannheim.korap.web.controller.VirtualCorpusController;
 import de.ids_mannheim.korap.web.input.QueryJson;
+import jakarta.persistence.NoResultException;
 import jakarta.ws.rs.core.Response.Status;
 
 /**
@@ -81,6 +84,8 @@
     @Autowired
     private RoleDao roleDao;
     @Autowired
+    private UserGroupDao userGroupDao;
+    @Autowired
     private UserGroupMemberDao memberDao;
 
     @Autowired
@@ -155,7 +160,7 @@
         return dtos;
     }
 
-    public void deleteQueryByName (String username, String queryName,
+    public void deleteQueryByName (String deletedBy, String queryName,
             String createdBy, QueryType type) throws KustvaktException {
 
         QueryDO query = queryDao.retrieveQueryByName(queryName, createdBy);
@@ -165,15 +170,13 @@
             throw new KustvaktException(StatusCodes.NO_RESOURCE_FOUND,
                     "Query " + code + " is not found.", String.valueOf(code));
         }
-        else if (query.getCreatedBy().equals(username)
-                || adminDao.isAdmin(username)) {
+        else if (query.getCreatedBy().equals(deletedBy)
+                || adminDao.isAdmin(deletedBy)) {
 
             if (query.getType().equals(ResourceType.PUBLISHED)) {
-                QueryAccess access = accessDao
-                        .retrieveHiddenAccess(query.getId());
-                accessDao.deleteAccess(access, "system");
-                userGroupService.deleteAutoHiddenGroup(
-                        access.getUserGroup().getId(), "system");
+                UserGroup group = userGroupDao
+                        .retrieveHiddenGroupByQueryName(queryName);
+                userGroupDao.deleteGroup(group.getId(), deletedBy, false);
             }
             if (type.equals(QueryType.VIRTUAL_CORPUS)
                     && VirtualCorpusCache.contains(queryName)) {
@@ -183,7 +186,7 @@
         }
         else {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
-                    "Unauthorized operation for user: " + username, username);
+                    "Unauthorized operation for user: " + deletedBy, deletedBy);
         }
     }
 
@@ -232,11 +235,10 @@
             if (existingQuery.getType().equals(ResourceType.PUBLISHED)) {
                 // withdraw from publication
                 if (!type.equals(ResourceType.PUBLISHED)) {
-                    QueryAccess hiddenAccess = accessDao
-                            .retrieveHiddenAccess(existingQuery.getId());
-                    deleteQueryAccess(hiddenAccess.getId(), "system");
-                    int groupId = hiddenAccess.getUserGroup().getId();
-                    userGroupService.deleteAutoHiddenGroup(groupId, "system");
+                    UserGroup group = userGroupDao
+                            .retrieveHiddenGroupByQueryName(queryName);
+                    int groupId = group.getId();
+                    userGroupDao.deleteGroup(groupId, username, false);
                     // EM: should the users within the hidden group
                     // receive
                     // notifications?
@@ -256,22 +258,22 @@
 
     private void publishQuery (int queryId) throws KustvaktException {
 
-        QueryAccess access = accessDao.retrieveHiddenAccess(queryId);
+//        QueryAccess access = accessDao.retrieveHiddenAccess(queryId);
         // check if hidden access exists
-        if (access == null) {
+//        if (access == null) {
             QueryDO query = queryDao.retrieveQueryById(queryId);
             // create and assign a new hidden group
             int groupId = userGroupService.createAutoHiddenGroup();
             UserGroup autoHidden = userGroupService
                     .retrieveUserGroupById(groupId);
-            accessDao.createAccessToQuery(query, autoHidden);
-//                    , "system", QueryAccessStatus.HIDDEN);
-        }
-        else {
-            // should not happened
-            jlog.error("Cannot publish query with id: " + queryId
-                    + ". Hidden access exists! Access id: " + access.getId());
-        }
+//            accessDao.createAccessToQuery(query, autoHidden);
+            addRoleToQuery(query, autoHidden);
+//        }
+//        else {
+//            // should not happened
+//            jlog.error("Cannot publish query with id: " + queryId
+//                    + ". Hidden access exists! Access id: " + access.getId());
+//        }
     }
 
     public void storeQuery (QueryJson query, String queryName,
@@ -493,7 +495,7 @@
         }
         else {
             try {
-                createAccessToQuery(query, userGroup);
+                addRoleToQuery(query, userGroup);
             }
             catch (Exception e) {
                 Throwable cause = e;
@@ -514,7 +516,7 @@
         }
     }
     
-    public void createAccessToQuery (QueryDO query, UserGroup userGroup)
+    public void addRoleToQuery (QueryDO query, UserGroup userGroup)
             throws KustvaktException {
     
         List<UserGroupMember> members = memberDao
@@ -577,18 +579,18 @@
         UserGroup userGroup = userGroupService
                 .retrieveUserGroupByName(groupName);
 
-        Set<Role> accessList;
+        Set<Role> roles;
         if (adminDao.isAdmin(username)
                 || userGroupService.isUserGroupAdmin(username, userGroup)) {
             //            accessList = accessDao.retrieveAllAccessByGroup(userGroup.getId());
-            accessList = roleDao.retrieveRoleByGroupId(userGroup.getId(), true);
+            roles = roleDao.retrieveRoleByGroupId(userGroup.getId(), true);
 
         }
         else {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
-        return accessConverter.createRoleDto(accessList);
+        return accessConverter.createRoleDto(roles);
     }
 
     public void deleteQueryAccess (int roleId, String username)
@@ -699,12 +701,28 @@
                     && !username.equals("guest")) {
                 // add user in the query's auto group
                 UserGroup userGroup = userGroupService
-                        .retrieveHiddenUserGroupByQuery(query.getId());
+                        .retrieveHiddenUserGroupByQueryId(query.getId());
                 try {
+                    
+                    Role r1= roleDao.retrieveRoleByPrivilegeAndQuery(
+                            PrivilegeType.READ_QUERY, query.getId());
+                    Set<Role> memberRoles = new HashSet<Role>();
+                    memberRoles.add(r1);
+                    
                     userGroupService.addGroupMember(username, userGroup,
-                            "system", GroupMemberStatus.ACTIVE);
+                            "system", GroupMemberStatus.ACTIVE, memberRoles);    
                     // member roles are not set (not necessary)
                 }
+                catch (NoResultException ne) {
+                    Role r1 = new Role(PredefinedRole.QUERY_ACCESS,
+                            PrivilegeType.READ_QUERY, userGroup);
+                    roleDao.addRole(r1);
+                    Set<Role> memberRoles = new HashSet<Role>();
+                    memberRoles.add(r1);
+                    
+                    userGroupService.addGroupMember(username, userGroup,
+                            "system", GroupMemberStatus.ACTIVE, memberRoles);                
+                }
                 catch (KustvaktException e) {
                     // member exists
                     // skip adding user to hidden group
diff --git a/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java b/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
index 1bd6e74..4bf3bff 100644
--- a/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
@@ -137,9 +137,18 @@
         return userGroupDao.retrieveGroupByName(groupName, false);
     }
 
-    public UserGroup retrieveHiddenUserGroupByQuery (int queryId)
+    public UserGroup retrieveHiddenUserGroupByQueryId (int queryId)
             throws KustvaktException {
-        return userGroupDao.retrieveHiddenGroupByQuery(queryId);
+        return userGroupDao.retrieveHiddenGroupByQueryId(queryId);
+    }
+    
+    public UserGroupDto retrieveHiddenUserGroupByQueryName (String queryName)
+            throws KustvaktException {
+        UserGroup group = userGroupDao
+                .retrieveHiddenGroupByQueryName(queryName);
+        List<UserGroupMember> members = groupMemberDao
+                .retrieveMemberByGroupId(group.getId());
+        return converter.createUserGroupDto(group, members, null, null);
     }
 
     public List<UserGroupDto> retrieveUserGroupByStatus (String username,
@@ -160,7 +169,7 @@
         }
         return dtos;
     }
-
+    
     public List<UserGroupMember> retrieveQueryAccessAdmins (UserGroup userGroup)
             throws KustvaktException {
         List<UserGroupMember> groupAdmins = groupMemberDao.retrieveMemberByRole(
@@ -222,7 +231,7 @@
         UserGroup userGroup = null;
         boolean groupExists = false;
         try {
-            userGroup = userGroupDao.retrieveGroupByName(groupName, false);
+            userGroup = retrieveUserGroupByName(groupName);
             groupExists = true;
         }
         catch (KustvaktException e) {
@@ -235,7 +244,7 @@
             try {
                 userGroupDao.createGroup(groupName, description, createdBy,
                         UserGroupStatus.ACTIVE);
-                userGroup = userGroupDao.retrieveGroupByName(groupName, false);
+                userGroup = retrieveUserGroupByName(groupName);
             }
             // handle DB exceptions, e.g. unique constraint
             catch (Exception e) {
@@ -261,8 +270,7 @@
 
     public void deleteGroup (String groupName, String username)
             throws KustvaktException {
-        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName,
-                false);
+        UserGroup userGroup = retrieveUserGroupByName(groupName);
         if (userGroup.getStatus() == UserGroupStatus.DELETED) {
             // EM: should this be "not found" instead?
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
@@ -290,13 +298,6 @@
         return groupId;
     }
 
-    public void deleteAutoHiddenGroup (int groupId, String deletedBy)
-            throws KustvaktException {
-        // default hard delete
-        userGroupDao.deleteGroup(groupId, deletedBy,
-                config.isSoftDeleteAutoGroup());
-    }
-
     /**
      * Adds a user to the specified usergroup. If the username with
      * {@link GroupMemberStatus} DELETED exists as a member of the
@@ -336,6 +337,12 @@
     public void addGroupMember (String username, UserGroup userGroup,
             String createdBy, GroupMemberStatus status)
             throws KustvaktException {
+        addGroupMember(username, userGroup, createdBy, status, null);
+    }
+    
+    public void addGroupMember (String username, UserGroup userGroup,
+            String createdBy, GroupMemberStatus status, Set<Role> roles)
+            throws KustvaktException {
         int groupId = userGroup.getId();
         ParameterChecker.checkIntegerValue(groupId, "userGroupId");
 
@@ -354,6 +361,9 @@
         member.setGroup(userGroup);
         member.setStatus(status);
         member.setUserId(username);
+        if (roles !=null) {
+            member.setRoles(roles);
+        }
         groupMemberDao.addMember(member);
     }
 
@@ -450,8 +460,7 @@
         ParameterChecker.checkStringValue(username, "userId");
         ParameterChecker.checkStringValue(groupName, "groupName");
 
-        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName,
-                false);
+        UserGroup userGroup = retrieveUserGroupByName(groupName);
         if (userGroup.getStatus() == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
                     "Group " + userGroup.getName() + " has been deleted.",
@@ -513,8 +522,7 @@
     public void deleteGroupMember (String memberId, String groupName,
             String deletedBy) throws KustvaktException {
 
-        UserGroup userGroup = userGroupDao.retrieveGroupByName(groupName,
-                false);
+        UserGroup userGroup = retrieveUserGroupByName(groupName);
         if (userGroup.getStatus() == UserGroupStatus.DELETED) {
             throw new KustvaktException(StatusCodes.GROUP_DELETED,
                     "Group " + userGroup.getName() + " has been deleted.",
diff --git a/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupAdminController.java b/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupAdminController.java
index 4ed6266..6af97be 100644
--- a/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupAdminController.java
+++ b/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupAdminController.java
@@ -59,6 +59,21 @@
             throw kustvaktResponseHandler.throwit(e);
         }
     }
+    
+    @POST
+    @Path("hidden")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public UserGroupDto getHiddenUserGroupForQuery (
+            @FormParam("queryName") String queryName) {
+        try {
+            return service.retrieveHiddenUserGroupByQueryName(queryName);
+        }
+        catch (KustvaktException e) {
+            throw kustvaktResponseHandler.throwit(e);
+        }
+    }
+    
+    
 
     /**
      * Retrieves a specific user-group. Only system admins are
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 6c91dd6..d7f937c 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
@@ -360,7 +360,7 @@
      */
     @DELETE
     @Path("access/{accessId}")
-    public Response deleteVCAccessById (
+    public Response deleteAccessById (
             @Context SecurityContext securityContext,
             @PathParam("accessId") int accessId) {
         TokenContext context = (TokenContext) securityContext
@@ -386,7 +386,7 @@
      */
     @GET
     @Path("access")
-    public List<QueryAccessDto> listVCAccesses (
+    public List<QueryAccessDto> listAccess (
             @Context SecurityContext securityContext,
             @QueryParam("groupName") String groupName) {
         TokenContext context = (TokenContext) securityContext
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
index 99e1454..b207fbc 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupControllerAdminTest.java
@@ -178,24 +178,9 @@
     }
 
     @Test
-    public void testListByStatusHidden ()
+    public void testListHiddenGroups ()
             throws ProcessingException, KustvaktException {
-        Form f = new Form();
-        f.param("status", "HIDDEN");
-        Response response = target().path(API_VERSION).path("admin")
-                .path("group").path("list").queryParam("status", "HIDDEN")
-                .request()
-                .header(Attributes.AUTHORIZATION,
-                        HttpAuthorizationHandler
-                                .createBasicAuthorizationHeaderValue(
-                                        sysAdminUser, "pass"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE,
-                        MediaType.APPLICATION_FORM_URLENCODED)
-                .post(Entity.form(f));
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-        String entity = response.readEntity(String.class);
-        JsonNode node = JsonUtils.readTree(entity);
+        JsonNode node = listHiddenGroup();
         assertEquals(1, node.size());
     }
 
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupTestBase.java b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupTestBase.java
index d07fad2..f05d05d 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupTestBase.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/usergroup/UserGroupTestBase.java
@@ -14,6 +14,7 @@
 import jakarta.ws.rs.ProcessingException;
 import jakarta.ws.rs.client.Entity;
 import jakarta.ws.rs.core.Form;
+import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.Response.Status;
 
@@ -139,8 +140,7 @@
         return node;
     }
 
-    protected JsonNode createMarlinGroup ()
-            throws ProcessingException, KustvaktException {
+    protected JsonNode createMarlinGroup () throws KustvaktException {
         Response response = createUserGroup(marlinGroupName,
                 "This is marlin-group.", "marlin");
         assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
@@ -148,5 +148,42 @@
         JsonNode node = JsonUtils.readTree(entity);
         return node;
     }
+    
+    protected JsonNode getHiddenGroup (String queryName)
+            throws KustvaktException {
+        Form f = new Form();
+        f.param("queryName", queryName);
+        Response response = target().path(API_VERSION).path("admin")
+                .path("group").path("hidden").request()
+                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+                        .createBasicAuthorizationHeaderValue("admin", "pass"))
+                .header(HttpHeaders.CONTENT_TYPE,
+                        MediaType.APPLICATION_FORM_URLENCODED)
+                .post(Entity.form(f));
+//        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        return node;
+    }
+    
+    protected JsonNode listHiddenGroup () throws KustvaktException {
+        Form f = new Form();
+        f.param("status", "HIDDEN");
+        Response response = target().path(API_VERSION).path("admin")
+                .path("group").path("list")
+                .request()
+                .header(Attributes.AUTHORIZATION,
+                        HttpAuthorizationHandler
+                                .createBasicAuthorizationHeaderValue(
+                                        "admin", "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE,
+                        MediaType.APPLICATION_FORM_URLENCODED)
+                .post(Entity.form(f));
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        String entity = response.readEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        return node;
+    }
 
 }
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
index 4b8e243..b27a593 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusControllerTest.java
@@ -8,15 +8,12 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
-import jakarta.ws.rs.ProcessingException;
-import jakarta.ws.rs.client.Entity;
-import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.Response.Status;
-
 import org.apache.http.entity.ContentType;
 import org.junit.jupiter.api.Test;
+
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.net.HttpHeaders;
+
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.constant.AuthenticationScheme;
@@ -24,6 +21,10 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.utils.JsonUtils;
+import jakarta.ws.rs.ProcessingException;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.Status;
 
 /**
  * @author margaretha
@@ -57,48 +58,6 @@
     }
 
     @Test
-    public void testCreatePublishedVC () throws KustvaktException {
-        String json = "{\"type\": \"PUBLISHED\""
-                + ",\"queryType\": \"VIRTUAL_CORPUS\""
-                + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
-        String vcName = "new-published-vc";
-        createVC(authHeader, testUser, vcName, json);
-        // test list owner vc
-        JsonNode node = retrieveVCInfo(testUser, testUser, vcName);
-        assertEquals(vcName, node.get("name").asText());
-        // EM: check hidden access
-        node = listAccessByGroup("admin", "");
-        node = node.get(node.size() - 1);
-        assertEquals(node.at("/createdBy").asText(), "system");
-        assertEquals(vcName, node.at("/queryName").asText());
-        assertTrue(node.at("/userGroupName").asText().startsWith("auto"));
-        assertEquals(vcName, node.at("/queryName").asText());
-        String groupName = node.at("/userGroupName").asText();
-        // EM: check if hidden group has been created
-        node = testCheckHiddenGroup(groupName);
-        assertEquals(node.at("/status").asText(), "HIDDEN");
-        // EM: delete vc
-        deleteVC(vcName, testUser, testUser);
-        // EM: check if the hidden groups are deleted as well
-        node = testCheckHiddenGroup(groupName);
-        assertEquals(StatusCodes.NO_RESOURCE_FOUND,
-                node.at("/errors/0/0").asInt());
-        assertEquals("Group " + groupName + " is not found",
-                node.at("/errors/0/1").asText());
-    }
-
-    private JsonNode testCheckHiddenGroup (String groupName)
-            throws ProcessingException, KustvaktException {
-        Response response = target().path(API_VERSION).path("admin")
-                .path("group").path("@" + groupName).request()
-                .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
-                        .createBasicAuthorizationHeaderValue("admin", "pass"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").post(null);
-        String entity = response.readEntity(String.class);
-        return JsonUtils.readTree(entity);
-    }
-
-    @Test
     public void testCreateVCWithInvalidToken ()
             throws IOException, KustvaktException {
         String json = "{\"type\": \"PRIVATE\","
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusListTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusListTest.java
index 01a491e..8db247d 100644
--- a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusListTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusListTest.java
@@ -26,7 +26,7 @@
         node = listSystemVC("nemo");
         assertEquals(1, node.size());
         node = listVC("nemo");
-        assertEquals(3, node.size());
+        assertEquals(2, node.size());
     }
 
     @Test
@@ -35,16 +35,26 @@
         JsonNode node = testListOwnerVC("pearl");
         assertEquals(0, node.size());
         node = listVC("pearl");
-        assertEquals(2, node.size());
+        assertEquals(1, node.size());
     }
 
     @Test
+    public void testListVCMarlin ()
+            throws ProcessingException, KustvaktException {
+        JsonNode node = testListOwnerVC("marlin");
+        assertEquals(2, node.size());
+        node = listVC("marlin");
+        assertEquals(3, node.size());
+    }
+
+    
+    @Test
     public void testListVCDory ()
             throws ProcessingException, KustvaktException {
         JsonNode node = testListOwnerVC("dory");
         assertEquals(2, node.size());
         node = listVC("dory");
-        assertEquals(4, node.size());
+        assertEquals(3, node.size());
     }
 
     @Test
diff --git a/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusPublishedTest.java b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusPublishedTest.java
new file mode 100644
index 0000000..2f711e3
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/web/controller/vc/VirtualCorpusPublishedTest.java
@@ -0,0 +1,122 @@
+package de.ids_mannheim.korap.web.controller.vc;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.PredefinedRole;
+import de.ids_mannheim.korap.constant.UserGroupStatus;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import jakarta.ws.rs.ProcessingException;
+
+public class VirtualCorpusPublishedTest extends VirtualCorpusTestBase{
+    
+    private String testUser = "vcPublishedTest";
+
+    @Test
+    public void testCreatePublishedVC () throws KustvaktException {
+        String vcName = "new-published-vc";
+        createPublishedVC(testUser, vcName);
+        
+        // test list owner vc
+        JsonNode node = retrieveVCInfo(testUser, testUser, vcName);
+        assertEquals(vcName, node.get("name").asText());
+        
+        node = getHiddenGroup(vcName);
+        assertEquals("system", node.at("/owner").asText());
+        assertEquals(UserGroupStatus.HIDDEN.name(), 
+                node.at("/status").asText());
+        
+        testAccessPublishedVC("gill", testUser, vcName);
+        
+        deleteVC(vcName, testUser, testUser);
+        
+        // EM: check if the hidden groups are deleted as well
+        node = getHiddenGroup(vcName);
+        assertEquals(StatusCodes.NO_RESOURCE_FOUND,
+                node.at("/errors/0/0").asInt());
+        assertEquals("No hidden group for query " + vcName + " is found",
+                node.at("/errors/0/1").asText());
+    }
+    
+    private void testAccessPublishedVC (String username, String vcCreator,
+            String vcName) throws ProcessingException, KustvaktException {
+        retrieveVCInfo(username, vcCreator, vcName);
+        
+        JsonNode node = getHiddenGroup(vcName);
+        System.out.println(node.toPrettyString());
+        assertEquals("system", node.at("/owner").asText());
+        assertEquals(UserGroupStatus.HIDDEN.name(), 
+                node.at("/status").asText());
+        assertEquals(username, node.at("/members/0/userId").asText());
+        assertEquals(GroupMemberStatus.ACTIVE.name(), 
+                node.at("/members/0/status").asText());
+        assertEquals(1, node.at("/members/0/roles").size());
+        assertEquals(PredefinedRole.QUERY_ACCESS.name(), 
+                node.at("/members/0/roles/0").asText());
+        String groupName = node.at("/name").asText();
+
+        node = listAccessByGroup("admin", groupName);
+        assertEquals(1, node.size());
+        assertEquals(vcName, node.at("/0/queryName").asText());
+        assertEquals(groupName, node.at("/0/userGroupName").asText());
+        assertEquals(1, node.at("/0/members").size());
+    }
+    
+    @Test
+    public void testMarlinPublishedVC () throws KustvaktException {
+        
+        JsonNode node = testListOwnerVC("marlin");
+        assertEquals(2, node.size());
+        node = listVC("marlin");
+        assertEquals(3, node.size());
+        
+        String vcName = "marlin-published-vc";
+        createPublishedVC("marlin", vcName);
+        
+        node = testListOwnerVC("marlin");
+        assertEquals(3, node.size());
+        node = listVC("marlin");
+        assertEquals(4, node.size());
+        
+        testSharePublishedVC(vcName);
+        
+        deleteVC(vcName, "marlin", "marlin");
+        
+        node = listAccessByGroup("admin", marlinGroupName);
+        assertEquals(0, node.size());
+        
+        deleteGroupByName(marlinGroupName, "marlin");
+    }
+    
+    private void testSharePublishedVC (String vcName) throws KustvaktException {
+        createMarlinGroup();
+        inviteMember(marlinGroupName, "marlin", "dory");
+        subscribe(marlinGroupName, "dory");
+
+        JsonNode node = listVC("dory");
+        assertEquals(3, node.size());
+
+        shareVC("marlin", vcName, marlinGroupName, "marlin");
+        
+        // check marlin-group access
+        node = listAccessByGroup("admin", marlinGroupName);
+        assertEquals(1, node.size());
+        assertEquals(vcName, node.at("/0/queryName").asText());
+        assertEquals(marlinGroupName, node.at("/0/userGroupName").asText());
+        assertEquals(2, node.at("/0/members").size());
+
+        // check hidden group access
+        node = getHiddenGroup(vcName);
+        String hiddenGroupName = node.at("/name").asText();
+        node = listAccessByGroup("admin", hiddenGroupName);
+        assertEquals(0, node.at("/0/members").size());
+        
+//        testAccessPublishedVC("dory", "marlin", vcName);
+    }
+    
+}
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 8a03ce1..0cdd70a 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
@@ -82,7 +82,7 @@
         
         node = listAccessByGroup("marlin", marlinGroupName);
         assertEquals(1, node.size());
-        System.out.println(node.toPrettyString());
+//        System.out.println(node.toPrettyString());
         deleteGroupByName(marlinGroupName, "marlin");
     }
 
@@ -120,6 +120,7 @@
         // check member roles
         node = listAccessByGroup(testUser, groupName);
         assertEquals(1, node.size());
+//        System.out.println(node.toPrettyString());
         
         // search by member
         response = searchWithVCRef(memberName, testUser, vcName);
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 3a0d385..d043460 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
@@ -209,4 +209,15 @@
                 .delete();
         return response;
     }
+    
+    protected void createPublishedVC (String username, String vcName)
+            throws KustvaktException {
+        String json = "{\"type\": \"PUBLISHED\""
+                + ",\"queryType\": \"VIRTUAL_CORPUS\""
+                + ",\"corpusQuery\": \"corpusSigle=GOE\"}";
+
+        String authHeader = HttpAuthorizationHandler
+                .createBasicAuthorizationHeaderValue(username, "pass");
+        createVC(authHeader, username, vcName, json);
+    }
 }