Implemented withdraw VC publication & add user to group.

Change-Id: Ic189d8fdec5055e2ac248f81fe6f6d11adb51645
diff --git a/full/Changes b/full/Changes
new file mode 100644
index 0000000..fa2d6e7
--- /dev/null
+++ b/full/Changes
@@ -0,0 +1,55 @@
+0.59.10	2018-01-23
+	- added sort VC by id (margaretha)
+	- added test cases regarding VC sharing (margaretha)
+	- implemented withdraw VC from publication (margaretha)
+	- added Changes file (margaretha)
+	- implemented add users to group (margaretha)
+	
+	
+0.59.9 2018-01-19
+	- restructured basic authentication (margaretha)
+	- fixed AuthenticationException to include authentication scheme (margaretha)
+	- fixed rewrite redundancy in collection rewrite (margaretha)
+	- fixed foundry rewrite for constituents (margaretha)
+	- introduced authentication methods, schemes and tokens (margaretha)
+	- implemented collection rewrite with multiple licenses (margaretha)
+	- fixed foundry rewrite for korap span without wrap node (margaretha)
+	- implemented list user group (margaretha)
+	- implemented delete VC task (margaretha)
+	- implemented create user-group, subscribe to usergroup, unsubscribe to user group tasks(margaretha)
+	- fixed handling JSON mapping exception for missing enums (margaretha)
+    - implemented list VC task (margaretha)
+    - include KoralQuery in VC list (margaretha)
+	- implemented edit VC task (margaretha)
+	- implemented publish VC task (margaretha)
+    - implemented share VC task (margaretha)
+    - implemented list only owned VC task (margaretha) 
+    - implemented list VC access task (margaretha)
+    - implemented search VC by id task (margaretha)
+    - implemented delete VC access (margaretha)
+    - implemented search for project VC (margaretha)
+    - added search VC related tests (margaretha)
+    - removed PredefinedUserGroup.ALL and related codes (margaretha)
+    - implemented search for published VC (margaretha)
+    
+
+0.59.8 2017-09-21
+	- restructured statistics service (margaretha)
+	- removed deprecated loader codes and tests (margaretha)
+	- removed old Spring java configurations (margaretha)
+	- implemented entity classes for the new database (margaretha)
+	- added MySQL codes regarding virtual corpus and for testing (margaretha)
+	- added dao methods regarding virtual corpus (margaretha)
+	- added similar SQL codes (to MySQL) for sqlite (margaretha)
+	- added dao methods regarding user groups (margaretha)
+	- restructured web-service codes into controller and logic/business-service(margaretha)
+	- implemented user role and privilege, and added tests (margaretha)
+	- prepared test suite using new database (margaretha)
+	- implemented UserGroupDao and tests (margaretha)
+	- fixed missing exceptions in JsonUtils (margaretha)
+	- restructured web filters and authentication codes (margaretha)
+	- implemented create/store VC (margaretha)
+	- fixed collection rewrite bug regarding availability with operation or (margaretha)
+	
+	
+    
\ No newline at end of file
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
index 958b38f..6bd621f 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
@@ -91,9 +91,12 @@
         if (isSoftDelete) {
             group.setStatus(UserGroupStatus.DELETED);
             group.setDeletedBy(deletedBy);
-            entityManager.persist(group);
+            entityManager.merge(group);
         }
         else {
+            if (!entityManager.contains(group)){
+                group = entityManager.merge(group);
+            }
             entityManager.remove(group);
         }
     }
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
index 02c1bc0..aeb413e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
@@ -60,7 +60,7 @@
         entityManager.persist(member);
     }
 
-    public void deleteMember (String userId, int groupId, boolean isSoftDelete)
+    public void deleteMember (String userId, int groupId, String deletedBy, boolean isSoftDelete)
             throws KustvaktException {
         ParameterChecker.checkStringValue(userId, "userId");
         ParameterChecker.checkIntegerValue(groupId, "groupId");
diff --git a/full/src/main/java/de/ids_mannheim/korap/dao/VirtualCorpusAccessDao.java b/full/src/main/java/de/ids_mannheim/korap/dao/VirtualCorpusAccessDao.java
index feba49a..82678cf 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dao/VirtualCorpusAccessDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dao/VirtualCorpusAccessDao.java
@@ -189,7 +189,10 @@
         entityManager.persist(vca);
     }
 
-    public void deleteAccess (VirtualCorpusAccess access) {
+    public void deleteAccess (VirtualCorpusAccess access, String deletedBy) {
+        // soft delete
+        
+        // hard delete
         if (!entityManager.contains(access)){
             access = entityManager.merge(access);
         }
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java b/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
index 9034ffa..568e674 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
+++ b/full/src/main/java/de/ids_mannheim/korap/entity/UserGroup.java
@@ -31,7 +31,7 @@
 @Getter
 @Entity
 @Table(name = "user_group")
-public class UserGroup {
+public class UserGroup implements Comparable<UserGroup> {
 
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -59,4 +59,16 @@
     public String toString () {
         return "id=" + id + ", name= " + name + ", createdBy= " + createdBy;
     }
+
+
+    @Override
+    public int compareTo (UserGroup o) {
+        if (this.getId() > o.getId()) {
+            return 1;
+        }
+        else if (this.getId() < o.getId()) {
+            return -1;
+        }
+        return 0;
+    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java b/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
index 0768a62..c238ae4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/UserGroupService.java
@@ -1,6 +1,7 @@
 package de.ids_mannheim.korap.service;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import org.springframework.beans.factory.annotation.Autowired;
@@ -18,6 +19,10 @@
 import de.ids_mannheim.korap.entity.UserGroup;
 import de.ids_mannheim.korap.entity.UserGroupMember;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.ParameterChecker;
 import de.ids_mannheim.korap.web.controller.UserGroupController;
 import de.ids_mannheim.korap.web.input.UserGroupJson;
 
@@ -39,7 +44,10 @@
     private RoleDao roleDao;
     @Autowired
     private UserGroupConverter converter;
+    @Autowired
+    private AuthenticationManagerIface authManager;
 
+    private static List<Role> memberRoles;
 
     /** Only users with {@link PredefinedRole#USER_GROUP_ADMIN} 
      * are allowed to see the members of the group.
@@ -55,7 +63,7 @@
 
         List<UserGroup> userGroups =
                 userGroupDao.retrieveGroupByUserId(username);
-
+        Collections.sort(userGroups);
         ArrayList<UserGroupDto> dtos = new ArrayList<>(userGroups.size());
 
         List<UserGroupMember> groupAdmins;
@@ -126,11 +134,7 @@
                 UserGroupStatus.ACTIVE);
         UserGroup group = userGroupDao.retrieveGroupById(groupId);
 
-        List<Role> roles = new ArrayList<Role>(2);
-        roles.add(roleDao
-                .retrieveRoleById(PredefinedRole.USER_GROUP_MEMBER.getId()));
-        roles.add(roleDao
-                .retrieveRoleById(PredefinedRole.VC_ACCESS_MEMBER.getId()));
+        setMemberRoles();
 
         UserGroupMember m;
         for (String memberId : groupJson.getMembers()) {
@@ -144,7 +148,17 @@
             m.setCreatedBy(username);
             m.setGroup(group);
             m.setStatus(GroupMemberStatus.PENDING);
-            m.setRoles(roles);
+            m.setRoles(memberRoles);
+        }
+    }
+
+    private void setMemberRoles () {
+        if (memberRoles == null) {
+            memberRoles = new ArrayList<Role>(2);
+            memberRoles.add(roleDao.retrieveRoleById(
+                    PredefinedRole.USER_GROUP_MEMBER.getId()));
+            memberRoles.add(roleDao
+                    .retrieveRoleById(PredefinedRole.VC_ACCESS_MEMBER.getId()));
         }
     }
 
@@ -156,25 +170,108 @@
         return groupId;
     }
 
-    public void addUserToGroup (String username, UserGroup userGroup)
+    public void deleteAutoHiddenGroup (int groupId, String deletedBy)
+            throws KustvaktException {
+        // default hard delete
+        userGroupDao.deleteGroup(groupId, deletedBy, false);
+    }
+
+    /** Adds a user to the specified usergroup. If the username with 
+     *  {@link GroupMemberStatus} DELETED exists as a member of the group, 
+     *  the entry will be deleted first, and a new entry will be added.
+     *  
+     *  If a username with other statuses exists, a KustvaktException will 
+     *  be thrown.    
+     * 
+     * @see GroupMemberStatus
+     * 
+     * @param username a username
+     * @param userGroup a user group
+     * @param createdBy the user (VCA/system) adding the user the user-group 
+     * @param status the status of the membership
+     * @throws KustvaktException
+     */
+    public void addUserToGroup (String username, UserGroup userGroup,
+            String createdBy, GroupMemberStatus status)
             throws KustvaktException {
 
-        List<Role> roles = new ArrayList<Role>(2);
-        roles.add(roleDao
-                .retrieveRoleById(PredefinedRole.USER_GROUP_MEMBER.getId()));
-        roles.add(roleDao
-                .retrieveRoleById(PredefinedRole.VC_ACCESS_MEMBER.getId()));
+        int groupId = userGroup.getId();
+        ParameterChecker.checkIntegerValue(groupId, "userGroupId");
+
+        if (memberExists(username, groupId, status)) {
+            throw new KustvaktException(StatusCodes.ENTRY_EXISTS,
+                    "Username: " + username + " with status " + status
+                            + " exists in usergroup " + userGroup.getName());
+        }
+
+        setMemberRoles();
 
         UserGroupMember member = new UserGroupMember();
-        member.setCreatedBy("system");
+        member.setCreatedBy(createdBy);
         member.setGroup(userGroup);
-        member.setRoles(roles);
-        member.setStatus(GroupMemberStatus.ACTIVE);
+        member.setRoles(memberRoles);
+        member.setStatus(status);
         member.setUserId(username);
 
         groupMemberDao.addMember(member);
     }
 
+    private boolean memberExists (String username, int groupId,
+            GroupMemberStatus status) throws KustvaktException {
+        UserGroupMember existingMember;
+        try {
+            existingMember =
+                    groupMemberDao.retrieveMemberById(username, groupId);
+        }
+        catch (KustvaktException e) {
+            return false;
+        }
+
+        GroupMemberStatus memberStatus = existingMember.getStatus();
+        if (memberStatus.equals(status)) {
+            return true;
+        }
+        else if (memberStatus.equals(GroupMemberStatus.DELETED)) {
+            // hard delete
+            groupMemberDao.deleteMember(username, groupId, "system", false);
+        }
+
+        return false;
+    }
+
+    public void addUsersToGroup (UserGroupJson group, String username)
+            throws KustvaktException {
+        int groupId = group.getId();
+        List<String> members = group.getMembers();
+        ParameterChecker.checkIntegerValue(groupId, "id");
+        ParameterChecker.checkObjectValue(members, "members");
+
+        UserGroup userGroup = retrieveUserGroupById(groupId);
+        User user = authManager.getUser(username);
+        if (isUserGroupAdmin(username, userGroup) || user.isAdmin()) {
+            for (String memberName : members) {
+                addUserToGroup(memberName, userGroup, username,
+                        GroupMemberStatus.PENDING);
+            }
+        }
+        else {
+            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                    "Unauthorized operation for user: " + username, username);
+        }
+    }
+
+    private boolean isUserGroupAdmin (String username, UserGroup userGroup)
+            throws KustvaktException {
+        List<UserGroupMember> userGroupAdmins =
+                retrieveUserGroupAdmins(userGroup);
+        for (UserGroupMember admin : userGroupAdmins) {
+            if (username.equals(admin.getUserId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** Updates the {@link GroupMemberStatus} of a pending member 
      * to {@link GroupMemberStatus#ACTIVE}.
      * 
@@ -197,7 +294,7 @@
      */
     public void unsubscribe (int groupId, String username)
             throws KustvaktException {
-        groupMemberDao.deleteMember(username, groupId, true);
+        groupMemberDao.deleteMember(username, groupId, username, true);
     }
 
 
@@ -217,4 +314,6 @@
     public UserGroup retrieveHiddenGroup (int vcId) throws KustvaktException {
         return userGroupDao.retrieveHiddenGroupByVC(vcId);
     }
+
+
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java b/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
index 434bb47..832dca0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/service/VirtualCorpusService.java
@@ -13,6 +13,8 @@
 import com.fasterxml.jackson.databind.JsonNode;
 
 import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.UserGroupStatus;
 import de.ids_mannheim.korap.constant.VirtualCorpusAccessStatus;
 import de.ids_mannheim.korap.constant.VirtualCorpusType;
 import de.ids_mannheim.korap.dao.VirtualCorpusAccessDao;
@@ -79,7 +81,7 @@
         List<VirtualCorpus> vcList = vcDao.retrieveVCByUser(username);
         return createVCDtos(vcList);
     }
-    
+
     private ArrayList<VirtualCorpusDto> createVCDtos (
             List<VirtualCorpus> vcList) throws KustvaktException {
         ArrayList<VirtualCorpusDto> dtos = new ArrayList<>(vcList.size());
@@ -119,16 +121,11 @@
 
     public void editVC (VirtualCorpusJson vcJson, String username)
             throws KustvaktException {
-
-        VirtualCorpus vc = vcDao.retrieveVCById(vcJson.getId());
-        editVC(vc, vcJson, username);
-    }
-
-    public void editVC (VirtualCorpus vc, VirtualCorpusJson vcJson,
-            String username) throws KustvaktException {
         ParameterChecker.checkIntegerValue(vcJson.getId(), "id");
-        User user = authManager.getUser(username);
+        int vcId = vcJson.getId();
+        VirtualCorpus vc = vcDao.retrieveVCById(vcId);
 
+        User user = authManager.getUser(username);
         if (!username.equals(vc.getCreatedBy()) && !user.isAdmin()) {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
@@ -142,14 +139,45 @@
             requiredAccess = determineRequiredAccess(koralQuery);
         }
 
+        VirtualCorpusType type = vcJson.getType();
+        if (type != null) {
+            if (vc.getType().equals(VirtualCorpusType.PUBLISHED)) {
+                // withdraw from publication
+                if (!type.equals(VirtualCorpusType.PUBLISHED)) {
+                    VirtualCorpusAccess hiddenAccess =
+                            accessDao.retrieveHiddenAccess(vcId);
+                    deleteVCAccess(hiddenAccess.getId(), "system");
+                    int groupId = hiddenAccess.getUserGroup().getId();
+                    userGroupService.deleteAutoHiddenGroup(groupId, "system");
+                }
+                // else remains the same
+            }
+            else if (type.equals(VirtualCorpusType.PUBLISHED)) {
+                publishVC(vcJson.getId());
+            }
+        }
+
         vcDao.editVirtualCorpus(vc, vcJson.getName(), vcJson.getType(),
                 requiredAccess, koralQuery, vcJson.getDefinition(),
                 vcJson.getDescription(), vcJson.getStatus());
+    }
 
-        if (!vc.getType().equals(VirtualCorpusType.PUBLISHED)
-                && vcJson.getType() != null
-                && vcJson.getType().equals(VirtualCorpusType.PUBLISHED)) {
-            publishVC(vcJson.getId());
+    private void publishVC (int vcId) throws KustvaktException {
+
+        VirtualCorpusAccess access = accessDao.retrieveHiddenAccess(vcId);
+        // check if hidden access exists
+        if (access == null) {
+            VirtualCorpus vc = vcDao.retrieveVCById(vcId);
+            // create and assign a hidden group
+            int groupId = userGroupService.createAutoHiddenGroup(vcId);
+            UserGroup autoHidden =
+                    userGroupService.retrieveUserGroupById(groupId);
+            accessDao.createAccessToVC(vc, autoHidden, "system",
+                    VirtualCorpusAccessStatus.HIDDEN);
+        }
+        else {
+            jlog.error("Cannot publish VC with id: " + vcId
+                    + ". There have been hidden accesses for the VC already.");
         }
     }
 
@@ -227,51 +255,6 @@
         return (numberOfDoc > 0) ? true : false;
     }
 
-    private void publishVC (int vcId) throws KustvaktException {
-
-        VirtualCorpusAccess access = accessDao.retrieveHiddenAccess(vcId);
-        // check if hidden access exists
-        if (access == null) {
-            VirtualCorpus vc = vcDao.retrieveVCById(vcId);
-            // create and assign a hidden group
-            int groupId = userGroupService.createAutoHiddenGroup(vcId);
-            UserGroup autoHidden =
-                    userGroupService.retrieveUserGroupById(groupId);
-            accessDao.createAccessToVC(vc, autoHidden, "system",
-                    VirtualCorpusAccessStatus.HIDDEN);
-        }
-        else {
-            jlog.error("Cannot publish VC with id: " + vcId
-                    + ". There have been hidden accesses for the VC already.");
-        }
-    }
-
-
-    //    public void concealVC (String username, int vcId) throws KustvaktException {
-    //
-    //        VirtualCorpus vc = vcDao.retrieveVCById(vcId);
-    //        if (vc.getType().equals(VirtualCorpusType.PUBLISHED)) {
-    //            throw new KustvaktException(StatusCodes.NOTHING_CHANGED,
-    //                    "Virtual corpus is not published.");
-    //        }
-    //
-    //        VirtualCorpusJson vcJson = new VirtualCorpusJson();
-    //        // EM: a published VC may originate from a project or a private VC. 
-    //        // This origin is not saved in the DB. To be on the safe side, 
-    //        // VirtualCorpusType is changed into PROJECT so that any groups 
-    //        // associated with the VC can access it.
-    //        vcJson.setType(VirtualCorpusType.PROJECT);
-    //        editVC(vc, vcJson, username);
-    //
-    //        List<VirtualCorpusAccess> hiddenAccess =
-    //                accessDao.retrieveHiddenAccess(vcId);
-    //        for (VirtualCorpusAccess access : hiddenAccess){
-    //            access.setDeletedBy(username);
-    //            editVCAccess(access,username);
-    //        }
-    //
-    //    }
-
     public List<VirtualCorpusAccess> retrieveAllVCAccess (int vcId)
             throws KustvaktException {
         return accessDao.retrieveAllAccessByVC(vcId);
@@ -369,7 +352,7 @@
         return accessConverter.createVCADto(accessList);
     }
 
-    public void deleteVCAccess (String username, int accessId)
+    public void deleteVCAccess (int accessId, String username)
             throws KustvaktException {
 
         User user = authManager.getUser(username);
@@ -377,7 +360,7 @@
         VirtualCorpusAccess access = accessDao.retrieveAccessById(accessId);
         UserGroup userGroup = access.getUserGroup();
         if (isVCAccessAdmin(userGroup, username) || user.isAdmin()) {
-            accessDao.deleteAccess(access);
+            accessDao.deleteAccess(access, username);
         }
         else {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
@@ -404,12 +387,19 @@
 
             else if (VirtualCorpusType.PUBLISHED.equals(type)) {
                 // add user in the VC's auto group 
-//                VirtualCorpusAccess access =
-//                        accessDao.retrieveHiddenAccess(vcId);
-//                UserGroup userGroup = access.getUserGroup();
-                UserGroup userGroup = userGroupService.retrieveHiddenGroup(vcId);
-                if (!userGroupService.isMember(username, userGroup)) {
-                    userGroupService.addUserToGroup(username, userGroup);
+                //                VirtualCorpusAccess access =
+                //                        accessDao.retrieveHiddenAccess(vcId);
+                //                UserGroup userGroup = access.getUserGroup();
+                UserGroup userGroup =
+                        userGroupService.retrieveHiddenGroup(vcId);
+//                if (!userGroupService.isMember(username, userGroup)) {
+                try{
+                    userGroupService.addUserToGroup(username, userGroup,
+                            "system", GroupMemberStatus.ACTIVE);
+                }
+                catch (KustvaktException e) {
+                    // member exists
+                    // skip adding user to hidden group
                 }
             }
             // else VirtualCorpusType.PREDEFINED
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
index 5b4fb6c..8679437 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
@@ -108,6 +108,21 @@
         }
     }
 
+    @POST
+    @Path("add")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response addUserToGroup (@Context SecurityContext securityContext,
+            UserGroupJson group) {
+        TokenContext context =
+                (TokenContext) securityContext.getUserPrincipal();
+        try {
+            service.addUsersToGroup(group, context.getUsername());
+            return Response.ok().build();
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
 
     @POST
     @Path("subscribe")
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
index 98a45cd..ef012d6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
@@ -206,21 +206,6 @@
         return Response.ok().build();
     }
 
-    //  @POST
-    //  @Path("conceal")
-    //  public Response concealPublishedVC (@Context SecurityContext securityContext,
-    //          @QueryParam("vcId") int vcId) {
-    //      TokenContext context =
-    //              (TokenContext) securityContext.getUserPrincipal();
-    //      try {
-    //          service.concealVC(context.getUsername(), vcId);
-    //      }
-    //      catch (KustvaktException e) {
-    //          throw responseHandler.throwit(e);
-    //      }
-    //      return Response.ok().build();
-    //  }
-
     /** VC can only be shared with a group, not individuals. 
      *  Only VCA admins are allowed to share VC and 
      *  the VC must have been created by themselves.
@@ -259,7 +244,7 @@
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
-            service.deleteVCAccess(context.getUsername(), accessId);
+            service.deleteVCAccess(accessId, context.getUsername());
         }
         catch (KustvaktException e) {
             throw responseHandler.throwit(e);
@@ -268,8 +253,9 @@
     }
 
 
-    /** Lists only active accesses to the specified virtual corpus.
-     * Only available to VCA and system admins.
+    /** Lists active VC accesses to the specified VC.
+     *  Only available to VCA and system admins.
+     *  For system admins, lists all VCA of the VC.
      * 
      * @see VirtualCorpusAccessStatus
      * 
@@ -295,8 +281,9 @@
         return Response.ok(result).build();
     }
 
-    /** Lists all VC-accesses available for a group. 
-     *  Only available to VCA and system admins.
+    /** Lists active VC-accesses available for a user-group. 
+     *  Only available to VCA and system admins. 
+     *  For system admins, list all VCA for the group.
      * 
      * @param securityContext
      * @param groupId a group id
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java b/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java
index 13f3fdd..87f17cb 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/input/UserGroupJson.java
@@ -9,6 +9,7 @@
 @Setter
 public class UserGroupJson {
 
+    private int id;
     private String name;
     private List<String> members;
 }
diff --git a/full/src/test/java/de/ids_mannheim/korap/service/VirtualCorpusServiceTest.java b/full/src/test/java/de/ids_mannheim/korap/service/VirtualCorpusServiceTest.java
index 4576ddf..e9511a4 100644
--- a/full/src/test/java/de/ids_mannheim/korap/service/VirtualCorpusServiceTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/service/VirtualCorpusServiceTest.java
@@ -10,8 +10,11 @@
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
+import de.ids_mannheim.korap.constant.UserGroupStatus;
 import de.ids_mannheim.korap.constant.VirtualCorpusAccessStatus;
 import de.ids_mannheim.korap.constant.VirtualCorpusType;
+import de.ids_mannheim.korap.dto.VirtualCorpusDto;
+import de.ids_mannheim.korap.entity.UserGroup;
 import de.ids_mannheim.korap.entity.VirtualCorpusAccess;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.web.input.VirtualCorpusJson;
@@ -24,9 +27,9 @@
     private VirtualCorpusService vcService;
 
     @Test
-    public void createPublishVC () throws KustvaktException {
+    public void createDeletePublishVC () throws KustvaktException {
         String username = "VirtualCorpusServiceTest";
-        
+
         VirtualCorpusJson vc = new VirtualCorpusJson();
         vc.setCorpusQuery("corpusSigle=GOE");
         vc.setCreatedBy(username);
@@ -34,16 +37,63 @@
         vc.setType(VirtualCorpusType.PUBLISHED);
         int vcId = vcService.storeVC(vc, "VirtualCorpusServiceTest");
 
-        List<VirtualCorpusAccess> accesses = vcService.retrieveAllVCAccess(vcId);
+        List<VirtualCorpusAccess> accesses =
+                vcService.retrieveAllVCAccess(vcId);
         assertEquals(1, accesses.size());
-        
+
         VirtualCorpusAccess access = accesses.get(0);
         assertEquals(VirtualCorpusAccessStatus.HIDDEN, access.getStatus());
-        
-        // delete VC
+
         vcService.deleteVC(username, vcId);
         accesses = vcService.retrieveAllVCAccess(vcId);
         assertEquals(0, accesses.size());
     }
 
+    @Test
+    public void testEditPublishVC () throws KustvaktException {
+        String username = "dory";
+        int vcId = 2;
+
+        VirtualCorpusJson vcJson = new VirtualCorpusJson();
+        vcJson.setId(vcId);
+        vcJson.setName("group VC published");
+        vcJson.setType(VirtualCorpusType.PUBLISHED);
+
+        vcService.editVC(vcJson, username);
+
+        // check VC
+        VirtualCorpusDto vcDto = vcService.searchVCById("dory", vcId);
+        assertEquals("group VC published", vcDto.getName());
+        assertEquals(VirtualCorpusType.PUBLISHED.displayName(), vcDto.getType());
+
+        // check access
+        List<VirtualCorpusAccess> accesses =
+                vcService.retrieveAllVCAccess(vcId);
+        assertEquals(2, accesses.size());
+
+        VirtualCorpusAccess access = accesses.get(1);
+        assertEquals(VirtualCorpusAccessStatus.HIDDEN, access.getStatus());
+        
+        // check auto hidden group
+        UserGroup autoHiddenGroup = access.getUserGroup();
+        assertEquals(UserGroupStatus.HIDDEN, autoHiddenGroup.getStatus());
+
+        // 2nd edit (withdraw from publication)
+        vcJson = new VirtualCorpusJson();
+        vcJson.setId(vcId);
+        vcJson.setName("group VC");
+        vcJson.setType(VirtualCorpusType.PROJECT);
+
+        vcService.editVC(vcJson, username);
+
+        // check VC
+        vcDto = vcService.searchVCById("dory", vcId);
+        assertEquals("group VC", vcDto.getName());
+        assertEquals(VirtualCorpusType.PROJECT.displayName(), vcDto.getType());
+
+        // check access
+        accesses = vcService.retrieveAllVCAccess(vcId);
+        assertEquals(1, accesses.size());
+    }
+
 }
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
index b6a9eae..fea07fb 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/UserGroupControllerTest.java
@@ -2,6 +2,8 @@
 
 import static org.junit.Assert.assertEquals;
 
+import java.util.ArrayList;
+
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 
@@ -20,6 +22,7 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.web.input.UserGroupJson;
 
 public class UserGroupControllerTest extends SpringJerseyTest {
 
@@ -40,15 +43,11 @@
         //        System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
 
-        JsonNode group;
-        for (int i = 0; i < node.size(); i++) {
-            group = node.get(i);
-            if (group.at("/id").asInt() == 2) {
-                assertEquals("dory group", group.at("/name").asText());
-                assertEquals("dory", group.at("/owner").asText());
-                assertEquals(3, group.at("/members").size());
-            }
-        }
+        JsonNode group = node.get(1);
+        assertEquals(2, group.at("/id").asInt());
+        assertEquals("dory group", group.at("/name").asText());
+        assertEquals("dory", group.at("/owner").asText());
+        assertEquals(3, group.at("/members").size());
     }
 
     // nemo is a group member in dory group
@@ -104,13 +103,33 @@
                 node.at("/errors/0/1").asText());
     }
 
+
+//    @Test
+//    public void testInviteMember () {
+//
+//    }
+//
+//    @Test
+//    public void testInviteDeletedMember () {
+//
+//    }
+//    
+//    @Test
+//    public void testDeletePendingMember () {
+//
+//    }
+
+    
     // marlin has GroupMemberStatus.PENDING in dory group
     @Test
-    public void testSubscribeUnsubscribeMarlinToDoryGroup () throws KustvaktException {
+    public void testSubscribeUnsubscribeMarlinToDoryGroup ()
+            throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("groupId", "2");
 
-        ClientResponse response = resource().path("group").path("subscribe")
+        ClientResponse response;
+        String entity;
+        response = resource().path("group").path("subscribe")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION,
@@ -127,7 +146,7 @@
                                 "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .get(ClientResponse.class);
-        String entity = response.getEntity(String.class);
+        entity = response.getEntity(String.class);
 
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
@@ -168,6 +187,24 @@
 
         node = JsonUtils.readTree(entity);
         assertEquals(1, node.size());
+
+        // add marlin to dory group again to set back the GroupMemberStatus.PENDING
+        ArrayList<String> members = new ArrayList<String>();
+        members.add("marlin");
+
+        UserGroupJson userGroup = new UserGroupJson();
+        userGroup.setMembers(members);
+        // dory group
+        userGroup.setId(2);
+
+        response = resource().path("group").path("add")
+                .type(MediaType.APPLICATION_JSON)
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue("dory",
+                                "pass"))
+                .entity(userGroup).post(ClientResponse.class);
+        entity = response.getEntity(String.class);
     }
 
     // pearl has GroupMemberStatus.DELETED in dory group
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
index 7e41f90..2b68543 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/VirtualCorpusControllerTest.java
@@ -36,6 +36,7 @@
 import de.ids_mannheim.korap.constant.VirtualCorpusType;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.service.VirtualCorpusServiceTest;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
 public class VirtualCorpusControllerTest extends SpringJerseyTest {
@@ -83,7 +84,7 @@
     }
 
     @Test
-    public void testOwnerSearchPrivateVC () throws UniformInterfaceException,
+    public void testSearchOwnerPrivateVC () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
 
         ClientResponse response = resource().path("vc").path("search").path("1")
@@ -309,7 +310,7 @@
     }
 
     @Test
-    public void testCreatePublishVC () throws KustvaktException {
+    public void testCreateDeletePublishVC () throws KustvaktException {
         String json =
                 "{\"name\": \"new published vc\",\"type\": \"PUBLISHED\",\"createdBy\": "
                         + "\"VirtualCorpusControllerTest\",\"corpusQuery\": \"corpusSigle=GOE\"}";
@@ -350,7 +351,7 @@
 
                 .delete(ClientResponse.class);
 
-        //EM: have to delete the hidden groups as well (admin)
+        //EM: check if the hidden groups are deleted as well (require system admin)
     }
 
     @Test
@@ -561,11 +562,39 @@
     }
 
     @Test
-    @Ignore
+    public void testEditVCNotOwner () throws KustvaktException {
+        String json = "{\"id\": \"1\", \"name\": \"edited vc\"}";
+
+        ClientResponse response = resource().path("vc").path("edit")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(
+                                "VirtualCorpusControllerTest", "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+                .post(ClientResponse.class, json);
+        String entity = response.getEntity(String.class);
+        //        System.out.println(entity);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+        assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+                node.at("/errors/0/0").asInt());
+        assertEquals(
+                "Unauthorized operation for user: VirtualCorpusControllerTest",
+                node.at("/errors/0/1").asText());
+
+        checkWWWAuthenticateHeader(response);
+    }
+    
+    
+    /**
+     * @see VirtualCorpusServiceTest
+     * @throws KustvaktException
+     */
+    @Test
     public void testEditPublishVC () throws KustvaktException {
 
         String json =
-                "{\"id\": \"1\", \"name\": \"dory published vc\", \"type\": \"PUBLISHED\"}";
+                "{\"id\": \"2\", \"type\": \"PUBLISHED\"}";
 
         ClientResponse response = resource().path("vc").path("edit")
                 .header(Attributes.AUTHORIZATION,
@@ -589,36 +618,43 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
         JsonNode node = JsonUtils.readTree(entity);
-        JsonNode n = node.get(0);
-        assertEquals("dory published vc", n.get("name").asText());
+        JsonNode n = node.get(1);
         assertEquals(VirtualCorpusType.PUBLISHED.displayName(),
                 n.get("type").asText());
-    }
+        
+        //check VC access
+        // need system admin account
+        
+        // edit 2nd
+        json =
+                "{\"id\": \"2\", \"type\": \"PROJECT\"}";
 
-    @Test
-    public void testEditVCNotOwner () throws KustvaktException {
-        String json = "{\"id\": \"1\", \"name\": \"edited vc\"}";
-
-        ClientResponse response = resource().path("vc").path("edit")
+        response = resource().path("vc").path("edit")
                 .header(Attributes.AUTHORIZATION,
-                        handler.createBasicAuthorizationHeaderValue(
-                                "VirtualCorpusControllerTest", "pass"))
+                        handler.createBasicAuthorizationHeaderValue("dory",
+                                "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
                 .post(ClientResponse.class, json);
-        String entity = response.getEntity(String.class);
-        //        System.out.println(entity);
-        JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.AUTHORIZATION_FAILED,
-                node.at("/errors/0/0").asInt());
-        assertEquals(
-                "Unauthorized operation for user: VirtualCorpusControllerTest",
-                node.at("/errors/0/1").asText());
 
-        checkWWWAuthenticateHeader(response);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+        
+        response = resource().path("vc").path("list").path("user")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue("dory",
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+
+                .get(ClientResponse.class);
+        entity = response.getEntity(String.class);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        node = JsonUtils.readTree(entity);
+        assertEquals(VirtualCorpusType.PROJECT.displayName(),
+                node.get(1).get("type").asText());
     }
 
+
     @Test
     public void testlistAccessByVC () throws KustvaktException {
         ClientResponse response = resource().path("vc").path("access")