Implemented delete user-group and member tasks.

Change-Id: Iacb799ca03234fbbd4b6cc06126cb78ed3521aad
diff --git a/README.md b/README.md
index 319603e..a744b66 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,11 @@
 </pre>
 
 Since Kustvakt requires Krill and Koral, please install [Krill](https://github.com/KorAP/Krill) and [Koral](https://github.com/KorAP/Koral) in your maven local repository.
-Adjust the versions of Krill and Koral in Kustvakt/core/pom.xml according to the versions in Koral/pom.xml and Krill/pom.xml.
+Adjust the versions of Krill and Koral in ```Kustvakt/core/pom.xml``` 
+according to the versions in 
+```Koral/pom.xml```
+ and 
+ ```Krill/pom.xml```.
 
 Install Kustvakt-core in your maven local repository
 <pre>
@@ -42,14 +46,14 @@
 cd ../full
 mvn clean package
 </pre>
-The jar file is located in the target/ folder.
+The jar file is located in the ```target/``` folder.
 
 Package Kustvakt lite version
 <pre>
 cd ../lite
 mvn clean package
 </pre>
-The jar file is located in the target/ folder.
+The jar file is located in the ```target/``` folder.
 
 If there are errors regarding tests, please skip them.
 <pre>
@@ -58,7 +62,7 @@
 
 # Setting kustvakt configuration file
 
-Copy the default Kustvakt configuration file (e.g. ```full/src/main/resources/kustvakt.conf``` or ```lite/src/main/resources/kustvakt-lite.conf```), to the same  folder as the Kustvakt jar files  (/target). Please do not change the name of the configuration file.
+Copy the default Kustvakt configuration file (e.g. ```full/src/main/resources/kustvakt.conf``` or ```lite/src/main/resources/kustvakt-lite.conf```), to the same  folder as the Kustvakt jar files  (```/target```). Please do not change the name of the configuration file.
 
 Set krill.indexDir in the configuration file to the location of your Krill index (relative path). In Kustvakt root directory, there is a sample index, e.g.
 <pre>krill.indexDir = ../../sample-index</pre>
@@ -94,14 +98,14 @@
 
 # Futher Setup for Developer
 
-Installing lombok is necessary when working with an IDE. Go to the directory of your lombok.jar, e.g \.m2\repository\org\projectlombok\lombok\1.16.6 and run
+Installing lombok is necessary when working with an IDE. Go to the directory of your lombok.jar, e.g ```~/.m2/repository/org/projectlombok/lombok/1.16.6``` and run
 <pre>
 java -jar lombok-1.16.6.jar
 </pre>
 
 Restart your IDE and clean your project.
 
-Copy ```kustvakt.conf``` or ```kustvakt-lite.conf``` from  src/main/resources to the full/ or lite/ folder. Then the properties the configuration file can be customized.
+Copy ```kustvakt.conf``` or ```kustvakt-lite.conf``` from  ```src/main/resources``` to the ```full/``` or ```lite/``` folder. Then the properties the configuration file can be customized.
 
 In an IDE, you can run ```KustvaktLiteServer``` or ```KustvaktServer``` as a normal Java application.
 
@@ -109,7 +113,7 @@
 
 The default Sqlite database can be switch to a MySQL database.
 
-Copy ```jdbc.properties``` from full/src/main/resources to the full/ directory. Do not change the filename.
+Copy ```jdbc.properties``` from ```full/src/main/resources``` to the ```full/``` directory. Do not change the filename.
 <pre>
 cp full/src/main/resources/jdbc.properties full/
 </pre>
@@ -131,9 +135,9 @@
 
 
 Open ```full/src/main/resource/default-config.xml``` and search for the 
-Spring bean with id="flyway".
+Spring bean with id "flyway".
 
-Change the dataSource property to refer to the Spring bean with id="dataSource".
+Change the dataSource property to refer to the Spring bean with id "dataSource".
 <pre>
 &lt;property name="dataSource" ref="dataSource" /&gt;
 </pre>
diff --git a/core/Changes b/core/Changes
new file mode 100644
index 0000000..c6ff1ba
--- /dev/null
+++ b/core/Changes
@@ -0,0 +1,13 @@
+0.59.10 2018-01-25 
+	- updated hibernate and reflection versions (margaretha)
+	- added Changes file (margaretha)
+	- merged BeanConfigBaseTest to BeanConfigTest in /full (margaretha)
+0.59.9 	2017-11-08
+	- fixed missing exception in JsonUtils (margaretha)
+	- fixed and restructured KustvaktResponseHandler (margaretha)
+	- updated status code in ParameterChecker (margaretha)
+0.59.8 2017-10-24
+	- restructured Kustvakt and created core project (margaretha)
+	- marked loader classes as deprecated (margaretha)
+	- updated Spring version (margaretha)
+	- moved unnecessary dependencies (margaretha)
\ No newline at end of file
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index 87465aa..ae96327 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -22,6 +22,7 @@
     public static final int CONNECTION_ERROR = 106;
     public static final int INVALID_ARGUMENT = 107;
     public static final int NOT_SUPPORTED = 108;
+    public static final int NOT_ALLOWED = 109;
     
     /**
      * 300 status codes for query language and serialization
diff --git a/core/src/main/java/de/ids_mannheim/korap/user/User.java b/core/src/main/java/de/ids_mannheim/korap/user/User.java
index 1de5d5d..8732a21 100644
--- a/core/src/main/java/de/ids_mannheim/korap/user/User.java
+++ b/core/src/main/java/de/ids_mannheim/korap/user/User.java
@@ -1,6 +1,15 @@
 package de.ids_mannheim.korap.user;
 
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.joda.time.DateTime;
+
 import com.fasterxml.jackson.databind.JsonNode;
+
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.ParamFields;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -11,13 +20,6 @@
 import lombok.Data;
 import lombok.Getter;
 import lombok.Setter;
-import org.joda.time.DateTime;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 
 @Data
 public abstract class User implements Serializable {
@@ -45,7 +47,7 @@
 
     private List<Userdata> userdata;
 
-    private boolean isAdmin;
+    private boolean isSystemAdmin;
 
     // Values for corpusAccess:
     public enum CorpusAccess	 {
diff --git a/full/Changes b/full/Changes
index fa2d6e7..0a8299a 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,10 +1,12 @@
-0.59.10	2018-01-23
+0.59.10	2018-01-25 
 	- 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)
-	
+	- implemented delete user-group and member tasks (margaretha)
+	- added userMemberStatus in group lists (margaretha)
+	- updated sql test data (margaretha)
 	
 0.59.9 2018-01-19
 	- restructured basic authentication (margaretha)
@@ -19,7 +21,7 @@
 	- 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)
+    - added KoralQuery in VC lists (margaretha)
 	- implemented edit VC task (margaretha)
 	- implemented publish VC task (margaretha)
     - implemented share VC task (margaretha)
diff --git a/full/pom.xml b/full/pom.xml
index 9b18889..02fc984 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -161,16 +161,9 @@
 		<dependency>
 			<groupId>de.ids_mannheim.korap</groupId>
 			<artifactId>Kustvakt-core</artifactId>
-			<version>0.59.9</version>
+			<version>0.59.10</version>
 			<type>jar</type>
 		</dependency>
-		<dependency>
-			<groupId>de.ids_mannheim.korap</groupId>
-			<artifactId>Kustvakt-core</artifactId>
-			<version>0.59.9</version>
-			<type>test-jar</type>
-			<scope>test</scope>
-		</dependency>
 		<!-- LDAP -->
 		<dependency>
 			<groupId>com.novell.ldap</groupId>
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java b/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
index d673427..46649eb 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/KustvaktAuthenticationManager.java
@@ -324,7 +324,7 @@
 		}
 
 		boolean isAdmin = adminHandler.isAdmin(unknown.getId());
-		unknown.setAdmin(isAdmin);
+		unknown.setSystemAdmin(isAdmin);
 		jlog.debug("Authentication: found username " + unknown.getUsername());
 
 		if (unknown instanceof KorAPUser) {
@@ -697,7 +697,7 @@
 
 		String o = (String) attributes.get(Attributes.IS_ADMIN);
 		boolean b = Boolean.parseBoolean(o);
-		user.setAdmin(b);
+		user.setSystemAdmin(b);
 
 		try {
 			UserDetails details = new UserDetails();
@@ -708,7 +708,7 @@
 
 			jlog.info("Creating new user account for user {}", user.getUsername());
 			entHandler.createAccount(user);
-			if (user.isAdmin() && user instanceof KorAPUser) {
+			if (user.isSystemAdmin() && user instanceof KorAPUser) {
 				adminHandler.addAccount(user);
 				user.setCorpusAccess(CorpusAccess.ALL);
 			}
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 6bd621f..6885f3d 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
@@ -172,8 +172,10 @@
                         UserGroupStatus.ACTIVE),
                 criteriaBuilder.equal(members.get(UserGroupMember_.userId),
                         userId),
-                criteriaBuilder.equal(members.get(UserGroupMember_.status),
-                        GroupMemberStatus.ACTIVE));
+                criteriaBuilder.notEqual(members.get(UserGroupMember_.status),
+                        GroupMemberStatus.DELETED));
+//                criteriaBuilder.equal(members.get(UserGroupMember_.status),
+//                        GroupMemberStatus.ACTIVE));
 
 
         query.select(root);
diff --git a/full/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java b/full/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java
index 10fc1c4..6b3e8b6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dto/UserGroupDto.java
@@ -2,6 +2,7 @@
 
 import java.util.List;
 
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
 import lombok.Getter;
 import lombok.Setter;
 
@@ -13,4 +14,5 @@
     private String name;
     private String owner;
     private List<UserGroupMemberDto> members;
+    private GroupMemberStatus userMemberStatus;
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/dto/converter/UserGroupConverter.java b/full/src/main/java/de/ids_mannheim/korap/dto/converter/UserGroupConverter.java
index 8925d20..273677b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/dto/converter/UserGroupConverter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/dto/converter/UserGroupConverter.java
@@ -5,6 +5,7 @@
 
 import org.springframework.stereotype.Component;
 
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
 import de.ids_mannheim.korap.dto.UserGroupDto;
 import de.ids_mannheim.korap.dto.UserGroupMemberDto;
 import de.ids_mannheim.korap.entity.Role;
@@ -15,12 +16,13 @@
 public class UserGroupConverter {
 
     public UserGroupDto createUserGroupDto (UserGroup group,
-            List<UserGroupMember> members) {
+            List<UserGroupMember> members, GroupMemberStatus userMemberStatus) {
 
         UserGroupDto dto = new UserGroupDto();
         dto.setId(group.getId());
         dto.setName(group.getName());
         dto.setOwner(group.getCreatedBy());
+        dto.setUserMemberStatus(userMemberStatus);
 
         if (members != null) {
             ArrayList<UserGroupMemberDto> memberDtos =
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 c238ae4..02cff45 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
@@ -66,30 +66,44 @@
         Collections.sort(userGroups);
         ArrayList<UserGroupDto> dtos = new ArrayList<>(userGroups.size());
 
-        List<UserGroupMember> groupAdmins;
+        UserGroupMember userAsMember;
+        List<UserGroupMember> members;
         for (UserGroup group : userGroups) {
-            groupAdmins = groupMemberDao.retrieveMemberByRole(group.getId(),
-                    PredefinedRole.USER_GROUP_ADMIN.getId());
-
-            List<UserGroupMember> members = null;
-            for (UserGroupMember admin : groupAdmins) {
-                if (admin.getUserId().equals(username)) {
-                    members = groupMemberDao
-                            .retrieveMemberByGroupId(group.getId());
-                    break;
-                }
-            }
-            dtos.add(converter.createUserGroupDto(group, members));
+            members = retrieveMembers(group.getId(), username);
+            userAsMember =
+                    groupMemberDao.retrieveMemberById(username, group.getId());
+            dtos.add(converter.createUserGroupDto(group, members,
+                    userAsMember.getStatus()));
         }
 
         return dtos;
     }
 
+    private List<UserGroupMember> retrieveMembers (int groupId, String username)
+            throws KustvaktException {
+        List<UserGroupMember> groupAdmins = groupMemberDao.retrieveMemberByRole(
+                groupId, PredefinedRole.USER_GROUP_ADMIN.getId());
+
+        List<UserGroupMember> members = null;
+        for (UserGroupMember admin : groupAdmins) {
+            if (admin.getUserId().equals(username)) {
+                members = groupMemberDao.retrieveMemberByGroupId(groupId);
+                break;
+            }
+        }
+        
+        return members;
+    }
+
     public UserGroup retrieveUserGroupById (int groupId)
             throws KustvaktException {
         return userGroupDao.retrieveGroupById(groupId);
     }
 
+    public UserGroup retrieveHiddenGroup (int vcId) throws KustvaktException {
+        return userGroupDao.retrieveHiddenGroupByVC(vcId);
+    }
+
     public List<UserGroupMember> retrieveVCAccessAdmins (UserGroup userGroup)
             throws KustvaktException {
         List<UserGroupMember> groupAdmins = groupMemberDao.retrieveMemberByRole(
@@ -104,6 +118,15 @@
         return groupAdmins;
     }
 
+    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()));
+        }
+    }
 
     /** Group owner is automatically added when creating a group. 
      *  Do not include owners in group members. 
@@ -122,43 +145,37 @@
      * @see /full/src/main/resources/db/predefined/V3.2__insert_predefined_roles.sql
      * 
      * @param groupJson UserGroupJson object from json
-     * @param username the user creating the group
+     * @param createdBy the user creating the group
      * @throws KustvaktException
      * 
      * 
      */
-    public void createUserGroup (UserGroupJson groupJson, String username)
+    public void createUserGroup (UserGroupJson groupJson, String createdBy)
             throws KustvaktException {
 
-        int groupId = userGroupDao.createGroup(groupJson.getName(), username,
+        int groupId = userGroupDao.createGroup(groupJson.getName(), createdBy,
                 UserGroupStatus.ACTIVE);
-        UserGroup group = userGroupDao.retrieveGroupById(groupId);
+        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId);
 
         setMemberRoles();
 
-        UserGroupMember m;
+
         for (String memberId : groupJson.getMembers()) {
-            if (memberId.equals(username)) {
+            if (memberId.equals(createdBy)) {
                 // skip owner, already added while creating group.
                 continue;
             }
-
-            m = new UserGroupMember();
-            m.setUserId(memberId);
-            m.setCreatedBy(username);
-            m.setGroup(group);
-            m.setStatus(GroupMemberStatus.PENDING);
-            m.setRoles(memberRoles);
+            addGroupMember(memberId, userGroup, createdBy,
+                    GroupMemberStatus.PENDING);
         }
     }
 
-    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()));
+    public void deleteGroup (int groupId, String username)
+            throws KustvaktException {
+        User user = authManager.getUser(username);
+        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId);
+        if (userGroup.getCreatedBy().equals(username) || user.isSystemAdmin()) {
+            userGroupDao.deleteGroup(groupId, username, false);
         }
     }
 
@@ -191,7 +208,7 @@
      * @param status the status of the membership
      * @throws KustvaktException
      */
-    public void addUserToGroup (String username, UserGroup userGroup,
+    public void addGroupMember (String username, UserGroup userGroup,
             String createdBy, GroupMemberStatus status)
             throws KustvaktException {
 
@@ -227,11 +244,12 @@
             return false;
         }
 
-        GroupMemberStatus memberStatus = existingMember.getStatus();
-        if (memberStatus.equals(status)) {
+        GroupMemberStatus existingStatus = existingMember.getStatus();
+        if (existingStatus.equals(GroupMemberStatus.ACTIVE)
+                || existingStatus.equals(status)) {
             return true;
         }
-        else if (memberStatus.equals(GroupMemberStatus.DELETED)) {
+        else if (existingStatus.equals(GroupMemberStatus.DELETED)) {
             // hard delete
             groupMemberDao.deleteMember(username, groupId, "system", false);
         }
@@ -242,15 +260,15 @@
     public void addUsersToGroup (UserGroupJson group, String username)
             throws KustvaktException {
         int groupId = group.getId();
-        List<String> members = group.getMembers();
+        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()) {
+        if (isUserGroupAdmin(username, userGroup) || user.isSystemAdmin()) {
             for (String memberName : members) {
-                addUserToGroup(memberName, userGroup, username,
+                addGroupMember(memberName, userGroup, username,
                         GroupMemberStatus.PENDING);
             }
         }
@@ -311,9 +329,23 @@
         return false;
     }
 
-    public UserGroup retrieveHiddenGroup (int vcId) throws KustvaktException {
-        return userGroupDao.retrieveHiddenGroupByVC(vcId);
+    public void deleteGroupMember (String memberId, int groupId,
+            String deletedBy) throws KustvaktException {
+        User user = authManager.getUser(deletedBy);
+        UserGroup userGroup = userGroupDao.retrieveGroupById(groupId);
+        if (memberId.equals(userGroup.getCreatedBy())) {
+            throw new KustvaktException(StatusCodes.NOT_ALLOWED,
+                    "Operation " + "'delete group owner'" + "is not allowed.",
+                    "delete group owner");
+        }
+        else if (isUserGroupAdmin(deletedBy, userGroup)
+                || user.isSystemAdmin()) {
+            groupMemberDao.deleteMember(memberId, groupId, deletedBy, false);
+        }
+        else {
+            throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+                    "Unauthorized operation for user: " + deletedBy, deletedBy);
+        }
     }
 
-
 }
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 832dca0..b4e7fbc 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
@@ -14,7 +14,6 @@
 
 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;
@@ -110,7 +109,7 @@
         User user = authManager.getUser(username);
         VirtualCorpus vc = vcDao.retrieveVCById(vcId);
 
-        if (vc.getCreatedBy().equals(username) || user.isAdmin()) {
+        if (vc.getCreatedBy().equals(username) || user.isSystemAdmin()) {
             vcDao.deleteVirtualCorpus(vcId);
         }
         else {
@@ -126,7 +125,7 @@
         VirtualCorpus vc = vcDao.retrieveVCById(vcId);
 
         User user = authManager.getUser(username);
-        if (!username.equals(vc.getCreatedBy()) && !user.isAdmin()) {
+        if (!username.equals(vc.getCreatedBy()) && !user.isSystemAdmin()) {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
@@ -192,7 +191,7 @@
         User user = authManager.getUser(username);
 
         if (vc.getType().equals(VirtualCorpusType.PREDEFINED)
-                && !user.isAdmin()) {
+                && !user.isSystemAdmin()) {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
@@ -266,20 +265,22 @@
         User user = authManager.getUser(username);
 
         VirtualCorpus vc = vcDao.retrieveVCById(vcId);
-        if (!username.equals(vc.getCreatedBy()) && !user.isAdmin()) {
+        if (!username.equals(vc.getCreatedBy()) && !user.isSystemAdmin()) {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
 
         UserGroup userGroup = userGroupService.retrieveUserGroupById(groupId);
 
-        if (!isVCAccessAdmin(userGroup, username) && !user.isAdmin()) {
+        if (!isVCAccessAdmin(userGroup, username) && !user.isSystemAdmin()) {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
         else {
             accessDao.createAccessToVC(vc, userGroup, username,
                     VirtualCorpusAccessStatus.ACTIVE);
+            vcDao.editVirtualCorpus(vc, null, VirtualCorpusType.PUBLISHED, null,
+                    null, null, null, null);
         }
     }
 
@@ -304,7 +305,7 @@
                 userGroupService.retrieveVCAccessAdmins(userGroup);
 
         User user = authManager.getUser(username);
-        if (!user.isAdmin()) {
+        if (!user.isSystemAdmin()) {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
                     "Unauthorized operation for user: " + username, username);
         }
@@ -315,7 +316,7 @@
 
         List<VirtualCorpusAccess> accessList;
         User user = authManager.getUser(username);
-        if (user.isAdmin()) {
+        if (user.isSystemAdmin()) {
             accessList = accessDao.retrieveAllAccessByVC(vcId);
         }
         else {
@@ -338,7 +339,7 @@
         UserGroup userGroup = userGroupService.retrieveUserGroupById(groupId);
 
         List<VirtualCorpusAccess> accessList;
-        if (user.isAdmin()) {
+        if (user.isSystemAdmin()) {
             accessList = accessDao.retrieveAllAccessByGroup(groupId);
         }
         else if (isVCAccessAdmin(userGroup, username)) {
@@ -359,7 +360,7 @@
 
         VirtualCorpusAccess access = accessDao.retrieveAccessById(accessId);
         UserGroup userGroup = access.getUserGroup();
-        if (isVCAccessAdmin(userGroup, username) || user.isAdmin()) {
+        if (isVCAccessAdmin(userGroup, username) || user.isSystemAdmin()) {
             accessDao.deleteAccess(access, username);
         }
         else {
@@ -376,7 +377,7 @@
         VirtualCorpus vc = vcDao.retrieveVCById(vcId);
         VirtualCorpusType type = vc.getType();
 
-        if (!user.isAdmin() && !username.equals(vc.getCreatedBy())) {
+        if (!user.isSystemAdmin() && !username.equals(vc.getCreatedBy())) {
             if (type.equals(VirtualCorpusType.PRIVATE)
                     || (type.equals(VirtualCorpusType.PROJECT)
                             && !hasAccess(username, vcId))) {
@@ -392,9 +393,9 @@
                 //                UserGroup userGroup = access.getUserGroup();
                 UserGroup userGroup =
                         userGroupService.retrieveHiddenGroup(vcId);
-//                if (!userGroupService.isMember(username, userGroup)) {
-                try{
-                    userGroupService.addUserToGroup(username, userGroup,
+                //                if (!userGroupService.isMember(username, userGroup)) {
+                try {
+                    userGroupService.addGroupMember(username, userGroup,
                             "system", GroupMemberStatus.ACTIVE);
                 }
                 catch (KustvaktException e) {
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 8679437..2ddec6f 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
@@ -3,11 +3,13 @@
 import java.util.List;
 
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.FormParam;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
@@ -31,13 +33,14 @@
 import de.ids_mannheim.korap.web.filter.PiwikFilter;
 import de.ids_mannheim.korap.web.input.UserGroupJson;
 
-/** UserGroupController defines web APIs related to user groups, 
- *  such as creating a user group,  listing groups of a user, 
- *  adding members to a group and subscribing (confirming an 
- *  invitation) to a group. 
- *  
- *  These APIs are only available to logged-in users.
- *   
+/**
+ * UserGroupController defines web APIs related to user groups,
+ * such as creating a user group, listing groups of a user,
+ * adding members to a group and subscribing (confirming an
+ * invitation) to a group.
+ * 
+ * These APIs are only available to logged-in users.
+ * 
  * @author margaretha
  *
  */
@@ -95,7 +98,7 @@
     @POST
     @Path("create")
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response createGroup (@Context SecurityContext securityContext,
+    public Response createUserGroup (@Context SecurityContext securityContext,
             UserGroupJson group) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
@@ -108,8 +111,50 @@
         }
     }
 
+    @DELETE
+    @Path("delete")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deleteUserGroup (
+            @Context SecurityContext securityContext,
+            @QueryParam("groupId") int groupId) {
+        TokenContext context =
+                (TokenContext) securityContext.getUserPrincipal();
+        try {
+            service.deleteGroup(groupId, context.getUsername());
+            return Response.ok().build();
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
+    
+    /** Group owner cannot be deleted.
+     * 
+     * @param securityContext
+     * @param memberId a username of a group member
+     * @param groupId a group id
+     * @return if successful, HTTP response status OK
+     */
+    @DELETE
+    @Path("member/delete")
+    @Consumes(MediaType.APPLICATION_JSON)
+    public Response deleteUserFromGroup (
+            @Context SecurityContext securityContext,
+            @QueryParam("memberId") String memberId,
+            @QueryParam("groupId") int groupId) {
+        TokenContext context =
+                (TokenContext) securityContext.getUserPrincipal();
+        try {
+            service.deleteGroupMember(memberId, groupId, context.getUsername());
+            return Response.ok().build();
+        }
+        catch (KustvaktException e) {
+            throw responseHandler.throwit(e);
+        }
+    }
+
     @POST
-    @Path("add")
+    @Path("member/invite")
     @Consumes(MediaType.APPLICATION_JSON)
     public Response addUserToGroup (@Context SecurityContext securityContext,
             UserGroupJson group) {
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
index b1cc781..bac128c 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/AdminFilter.java
@@ -71,7 +71,7 @@
             // EM: fix me: AuthenticationType based on header value
             User user = authManager.authenticate(AuthenticationMethod.LDAP,
                     data.getUsername(), data.getPassword(), attributes);
-            if (!user.isAdmin()) {
+            if (!user.isSystemAdmin()) {
                 throw new KustvaktException(StatusCodes.AUTHENTICATION_FAILED,
                         "Admin authentication failed.");
             }
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
index 758baf8..4cc7f99 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/BlockingFilter.java
@@ -44,7 +44,7 @@
         if (context == null || context.isDemo()) {
             throw kustvaktResponseHandler.throwit(new KustvaktException(
                     StatusCodes.AUTHORIZATION_FAILED,
-                    "Operation is not permitted for user: guest", "guest"));
+                    "Unauthorized operation for user: guest", "guest"));
         }
 
 
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 87f17cb..e3c25bd 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
@@ -1,7 +1,5 @@
 package de.ids_mannheim.korap.web.input;
 
-import java.util.List;
-
 import lombok.Getter;
 import lombok.Setter;
 
@@ -11,5 +9,5 @@
 
     private int id;
     private String name;
-    private List<String> members;
+    private String[] members;
 }
diff --git a/full/src/main/resources/db/insert/V3.3__insert_member_roles.sql b/full/src/main/resources/db/insert/V3.3__insert_member_roles.sql
index 9cb523f..effbbcb 100644
--- a/full/src/main/resources/db/insert/V3.3__insert_member_roles.sql
+++ b/full/src/main/resources/db/insert/V3.3__insert_member_roles.sql
@@ -4,49 +4,49 @@
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="marlin" AND group_id=1),
-	(SELECT id FROM role WHERE name = "group admin");
+	(SELECT id FROM role WHERE name = "USER_GROUP_ADMIN");
 	
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="marlin" AND group_id=1),
-	(SELECT id FROM role WHERE name = "vc admin");
+	(SELECT id FROM role WHERE name = "VC_ACCESS_ADMIN");
 	
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="dory" AND group_id=1),
-	(SELECT id FROM role WHERE name = "group member");
+	(SELECT id FROM role WHERE name = "USER_GROUP_ADMIN");
 	
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="dory" AND group_id=1),
-	(SELECT id FROM role WHERE name = "vc admin");
+	(SELECT id FROM role WHERE name = "VC_ACCESS_ADMIN");
 	
 	
 -- dory group
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="dory" AND group_id=2),
-	(SELECT id FROM role WHERE name = "group admin");
+	(SELECT id FROM role WHERE name = "USER_GROUP_ADMIN");
 	
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="dory" AND group_id=2),
-	(SELECT id FROM role WHERE name = "vc admin");
+	(SELECT id FROM role WHERE name = "VC_ACCESS_ADMIN");
 	
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="nemo" AND group_id=2),
-	(SELECT id FROM role WHERE name = "group member");
+	(SELECT id FROM role WHERE name = "USER_GROUP_MEMBER");
 	
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="nemo" AND group_id=2),
-	(SELECT id FROM role WHERE name = "vc member");
+	(SELECT id FROM role WHERE name = "VC_ACCESS_MEMBER");
 
 
 -- auto group
 INSERT INTO group_member_role(group_member_id,role_id)
 SELECT
 	(SELECT id FROM user_group_member WHERE user_id="pearl" AND group_id=3),
-	(SELECT id FROM role WHERE name = "vc member");
+	(SELECT id FROM role WHERE name = "VC_ACCESS_MEMBER");
 
diff --git a/full/src/main/resources/db/predefined/V3.2__insert_predefined_roles.sql b/full/src/main/resources/db/predefined/V3.2__insert_predefined_roles.sql
index c9409ea..6fb6991 100644
--- a/full/src/main/resources/db/predefined/V3.2__insert_predefined_roles.sql
+++ b/full/src/main/resources/db/predefined/V3.2__insert_predefined_roles.sql
@@ -1,8 +1,8 @@
 -- roles
-INSERT INTO role(name) VALUES ("group admin");
-INSERT INTO role(name) VALUES ("group member");
-INSERT INTO role(name) VALUES ("vc admin");
-INSERT INTO role(name) VALUES ("vc member");
+INSERT INTO role(name) VALUES ("USER_GROUP_ADMIN");
+INSERT INTO role(name) VALUES ("USER_GROUP_MEMBER");
+INSERT INTO role(name) VALUES ("VC_ACCESS_ADMIN");
+INSERT INTO role(name) VALUES ("VC_ACCESS_MEMBER");
 
 -- privileges
 INSERT INTO privilege(name,role_id)
diff --git a/core/src/test/java/de/ids_mannheim/korap/config/BeanConfigBaseTest.java b/full/src/test/java/de/ids_mannheim/korap/config/BeanConfigBaseTest.java
similarity index 99%
rename from core/src/test/java/de/ids_mannheim/korap/config/BeanConfigBaseTest.java
rename to full/src/test/java/de/ids_mannheim/korap/config/BeanConfigBaseTest.java
index 06cefe3..a625556 100644
--- a/core/src/test/java/de/ids_mannheim/korap/config/BeanConfigBaseTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/config/BeanConfigBaseTest.java
@@ -19,6 +19,7 @@
  * @author hanl
  * @date 09/03/2016
  */
+@Deprecated
 @NotThreadSafe
 @RunWith(BeanConfigBaseTest.SpringExtendedSetupListener.class)
 @ContextConfiguration("classpath:test-config.xml")
diff --git a/full/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java b/full/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java
index ff19b62..7603c18 100644
--- a/full/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java
@@ -1,12 +1,84 @@
 package de.ids_mannheim.korap.config;
 
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.log4j.Logger;
+import org.junit.runner.RunWith;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.model.InitializationError;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import net.jcip.annotations.NotThreadSafe;
+
 /**
  * @author hanl
  * @date 09/03/2016
  */
-public abstract class BeanConfigTest extends BeanConfigBaseTest{
+@NotThreadSafe
+@RunWith(BeanConfigTest.SpringExtendedSetupListener.class)
+@ContextConfiguration("classpath:test-config.xml")
+@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
+public abstract class BeanConfigTest {
 
-    @Override
+    private static Logger jlog = Logger.getLogger(BeanConfigTest.class);
+    @Autowired
+    protected ApplicationContext context;
+
+    public void init () throws Exception {
+        assertNotNull("Application context must not be null!", this.context);
+        jlog.debug("running one-time before init for class "
+                + this.getClass().getSimpleName() + " ...");
+        BeansFactory.setKustvaktContext(getContext());
+        assertNotNull(BeansFactory.getKustvaktContext());
+        initMethod();
+    }
+
+    public abstract void initMethod () throws KustvaktException;
+
+    public void close () {
+        BeansFactory.closeApplication();
+    }
+
+    public static class SpringExtendedSetupListener
+            extends SpringJUnit4ClassRunner {
+
+        private BeanConfigTest instanceSetupListener;
+
+
+        public SpringExtendedSetupListener (Class<?> clazz)
+                throws InitializationError {
+            super(clazz);
+        }
+
+
+        @Override
+        protected Object createTest () throws Exception {
+            Object test = super.createTest();
+            // Note that JUnit4 will call this createTest() multiple times for each
+            // test method, so we need to ensure to call "beforeClassSetup" only once.
+            if (test instanceof BeanConfigTest
+                    && instanceSetupListener == null) {
+                instanceSetupListener = (BeanConfigTest) test;
+                instanceSetupListener.init();
+            }
+            return test;
+        }
+
+
+        @Override
+        public void run (RunNotifier notifier) {
+            super.run(notifier);
+            if (instanceSetupListener != null) {
+                instanceSetupListener.close();
+            }
+        }
+    }
+
     protected ContextHolder getContext () {
         return helper().getContext();
     }
@@ -19,5 +91,4 @@
             return null;
         }
     }
-
 }
diff --git a/full/src/test/java/de/ids_mannheim/korap/dao/RolePrivilegeDaoTest.java b/full/src/test/java/de/ids_mannheim/korap/dao/RolePrivilegeDaoTest.java
index d7317a6..a4d770b 100644
--- a/full/src/test/java/de/ids_mannheim/korap/dao/RolePrivilegeDaoTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/dao/RolePrivilegeDaoTest.java
@@ -49,21 +49,21 @@
 
     @Test
     public void updateRole () {
-        Role role = roleDao.retrieveRoleByName("group member");
-        roleDao.editRoleName(role.getId(), "group member role");
+        Role role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
+        roleDao.editRoleName(role.getId(), "USER_GROUP_MEMBER role");
 
         role = roleDao.retrieveRoleById(role.getId());
-        assertEquals("group member role", role.getName());
+        assertEquals("USER_GROUP_MEMBER role", role.getName());
 
-        roleDao.editRoleName(role.getId(), "group member");
+        roleDao.editRoleName(role.getId(), "USER_GROUP_MEMBER");
         role = roleDao.retrieveRoleById(role.getId());
-        assertEquals("group member", role.getName());
+        assertEquals("USER_GROUP_MEMBER", role.getName());
     }
 
 
     @Test
     public void addDeletePrivilegeOfExistingRole () {
-        Role role = roleDao.retrieveRoleByName("group member");
+        Role role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
         List<Privilege> privileges = role.getPrivileges();
         assertEquals(1, role.getPrivileges().size());
         assertEquals(privileges.get(0).getName(), PrivilegeType.DELETE);
@@ -73,13 +73,13 @@
         privilegeTypes.add(PrivilegeType.READ);
         privilegeDao.addPrivilegesToRole(role, privilegeTypes);
 
-        role = roleDao.retrieveRoleByName("group member");
+        role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
         assertEquals(2, role.getPrivileges().size());
 
         //delete privilege
         privilegeDao.deletePrivilegeFromRole(role.getId(), PrivilegeType.READ);
 
-        role = roleDao.retrieveRoleByName("group member");
+        role = roleDao.retrieveRoleByName("USER_GROUP_MEMBER");
         assertEquals(1, role.getPrivileges().size());
         assertEquals(privileges.get(0).getName(), PrivilegeType.DELETE);
     }
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/FastJerseyTest.java b/full/src/test/java/de/ids_mannheim/korap/web/FastJerseyTest.java
index 3ac9366..d576a26 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/FastJerseyTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/FastJerseyTest.java
@@ -22,11 +22,9 @@
 import com.sun.jersey.test.framework.spi.container.grizzly.GrizzlyTestContainerFactory;
 import com.sun.jersey.test.framework.spi.container.grizzly.web.GrizzlyWebTestContainerFactory;
 
-import de.ids_mannheim.korap.config.BeanConfigBaseTest;
-import de.ids_mannheim.korap.config.ContextHolder;
-import de.ids_mannheim.korap.config.TestHelper;
+import de.ids_mannheim.korap.config.BeanConfigTest;
 
-public abstract class FastJerseyTest extends BeanConfigBaseTest{
+public abstract class FastJerseyTest extends BeanConfigTest{
 
     private static String[] classPackages =
             new String[] { "de.ids_mannheim.korap.web.service.full",
@@ -114,20 +112,20 @@
         return client.resource(getBaseUri());
     }
     
-    protected TestHelper helper () {
-        try {
-            return TestHelper.newInstance(this.context);
-        }
-        catch (Exception e) {
-            return null;
-        }
-    }
-
-
-    @Override
-    protected ContextHolder getContext () {
-        return helper().getContext();
-    }
+//    protected TestHelper helper () {
+//        try {
+//            return TestHelper.newInstance(this.context);
+//        }
+//        catch (Exception e) {
+//            return null;
+//        }
+//    }
+//
+//
+//    @Override
+//    protected ContextHolder getContext () {
+//        return helper().getContext();
+//    }
 
 
     public static void startServer () {
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchWithAvailabilityTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchWithAvailabilityTest.java
index 73e6880..02abd36 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchWithAvailabilityTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchWithAvailabilityTest.java
@@ -14,20 +14,14 @@
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.config.TokenType;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.utils.JsonUtils;
-import de.ids_mannheim.korap.web.FastJerseyTest;
 
-public class SearchWithAvailabilityTest extends FastJerseyTest {
+public class SearchWithAvailabilityTest extends SpringJerseyTest {
     @Autowired
-    HttpAuthorizationHandler handler;
+    private HttpAuthorizationHandler handler;
     
-    @Override
-    public void initMethod () throws KustvaktException {
-        //        helper().runBootInterfaces();
-    }
-
     private void checkAndFree (String json) throws KustvaktException {
         JsonNode node = JsonUtils.readTree(json);
         assertEquals("availability",
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
index b043d69..6fb0dd7 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/StatisticsControllerTest.java
@@ -1,33 +1,28 @@
 package de.ids_mannheim.korap.web.controller;
 
+import static org.junit.Assert.assertEquals;
+
 import java.io.IOException;
 
 import org.junit.Test;
-import static org.junit.Assert.assertEquals;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.sun.jersey.api.client.ClientResponse;
 
-import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.web.FastJerseyTest;
+import de.ids_mannheim.korap.config.SpringJerseyTest;
 
 /** 
  * @author margaretha
  * @date 27/09/2017
  *
  */
-public class StatisticsControllerTest extends FastJerseyTest {
+public class StatisticsControllerTest extends SpringJerseyTest {
 
     private ObjectMapper mapper = new ObjectMapper();
 
 
-    @Override
-    public void initMethod () throws KustvaktException {
-
-    }
-
     @Test
     public void testGetStatisticsNoResource ()
             throws JsonProcessingException, IOException {
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 fea07fb..edafea8 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,8 +2,6 @@
 
 import static org.junit.Assert.assertEquals;
 
-import java.util.ArrayList;
-
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 
@@ -12,13 +10,17 @@
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.ClientResponse.Status;
+import com.sun.jersey.api.client.UniformInterfaceException;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.SpringJerseyTest;
+import de.ids_mannheim.korap.constant.GroupMemberStatus;
+import de.ids_mannheim.korap.constant.PredefinedRole;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.utils.JsonUtils;
@@ -28,10 +30,11 @@
 
     @Autowired
     private HttpAuthorizationHandler handler;
+    private String username = "UserGroupControllerTest";
 
     // dory is a group admin in dory group
     @Test
-    public void testRetrieveDoryGroups () throws KustvaktException {
+    public void testListDoryGroups () throws KustvaktException {
         ClientResponse response = resource().path("group").path("list")
                 .header(Attributes.AUTHORIZATION,
                         handler.createBasicAuthorizationHeaderValue("dory",
@@ -52,7 +55,7 @@
 
     // nemo is a group member in dory group
     @Test
-    public void testRetrieveNemoGroups () throws KustvaktException {
+    public void testListNemoGroups () throws KustvaktException {
         ClientResponse response = resource().path("group").path("list")
                 .header(Attributes.AUTHORIZATION,
                         handler.createBasicAuthorizationHeaderValue("nemo",
@@ -71,9 +74,9 @@
         assertEquals(0, node.at("/0/members").size());
     }
 
-    // marlin has a group
+    // marlin has 2 groups
     @Test
-    public void testRetrieveMarlinGroups () throws KustvaktException {
+    public void testListMarlinGroups () throws KustvaktException {
         ClientResponse response = resource().path("group").path("list")
                 .header(Attributes.AUTHORIZATION,
                         handler.createBasicAuthorizationHeaderValue("marlin",
@@ -83,12 +86,12 @@
         String entity = response.getEntity(String.class);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
         JsonNode node = JsonUtils.readTree(entity);
-        assertEquals(1, node.size());
+        assertEquals(2, node.size());
     }
 
 
     @Test
-    public void testRetrieveUserGroupUnauthorized () throws KustvaktException {
+    public void testListUserGroupUnauthorized () throws KustvaktException {
         ClientResponse response = resource().path("group").path("list")
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .get(ClientResponse.class);
@@ -99,37 +102,192 @@
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
         assertEquals(StatusCodes.AUTHORIZATION_FAILED,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Operation is not permitted for user: guest",
+        assertEquals("Unauthorized operation for user: guest",
                 node.at("/errors/0/1").asText());
     }
 
+    @Test
+    public void testCreateUserGroup () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
 
-//    @Test
-//    public void testInviteMember () {
-//
-//    }
-//
-//    @Test
-//    public void testInviteDeletedMember () {
-//
-//    }
-//    
-//    @Test
-//    public void testDeletePendingMember () {
-//
-//    }
+        UserGroupJson json = new UserGroupJson();
+        json.setName("new user group");
+        json.setMembers(new String[] { "marlin", "nemo" });
 
-    
+        ClientResponse response = resource().path("group").path("create")
+                .type(MediaType.APPLICATION_JSON)
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(username,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32").entity(json)
+                .post(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        // list user group
+        response = resource().path("group").path("list")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(username,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .get(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        //        System.out.println(entity);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(1, node.size());
+        node = node.get(0);
+        assertEquals("new user group", node.get("name").asText());
+        String groupId = node.get("id").asText();
+
+        assertEquals(username, node.get("owner").asText());
+        assertEquals(3, node.get("members").size());
+        assertEquals(username, node.at("/members/0/userId").asText());
+        assertEquals(GroupMemberStatus.ACTIVE.name(),
+                node.at("/members/0/status").asText());
+        assertEquals(PredefinedRole.USER_GROUP_ADMIN.name(),
+                node.at("/members/0/roles/0").asText());
+        assertEquals(PredefinedRole.VC_ACCESS_ADMIN.name(),
+                node.at("/members/0/roles/1").asText());
+
+        assertEquals("marlin", node.at("/members/1/userId").asText());
+        assertEquals(GroupMemberStatus.PENDING.name(),
+                node.at("/members/1/status").asText());
+        assertEquals(PredefinedRole.USER_GROUP_MEMBER.name(),
+                node.at("/members/1/roles/0").asText());
+        assertEquals(PredefinedRole.VC_ACCESS_MEMBER.name(),
+                node.at("/members/1/roles/1").asText());
+
+        testDeleteGroupMemberUnauthorized(groupId);
+        testDeleteGroupMember(groupId);
+        testDeleteGroup(groupId);
+    }
+
+    private void testDeleteGroupMemberUnauthorized (String groupId)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        // nemo is a group member
+        ClientResponse response = resource().path("group").path("member")
+                .path("delete").queryParam("memberId", "marlin")
+                .queryParam("groupId", groupId)
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue("nemo",
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .delete(ClientResponse.class);
+
+        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: nemo",
+                node.at("/errors/0/1").asText());
+    }
+
+    private void testDeleteGroupMember (String groupId)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        // delete marlin from group
+        ClientResponse response = resource().path("group").path("member")
+                .path("delete").queryParam("memberId", "marlin")
+                .queryParam("groupId", groupId)
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(username,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .delete(ClientResponse.class);
+
+        // check group member
+        response = resource().path("group").path("list")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(username,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .get(ClientResponse.class);
+        String entity = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(entity);
+        node = node.get(0);
+        assertEquals(2, node.get("members").size());
+        assertEquals("nemo", node.at("/members/1/userId").asText());
+        assertEquals(GroupMemberStatus.PENDING.name(),
+                node.at("/members/1/status").asText());
+
+    }
+
+    private void testDeleteGroup (String groupId)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        //delete group
+        ClientResponse response = resource().path("group").path("delete")
+                .queryParam("groupId", groupId)
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(username,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .delete(ClientResponse.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        // check group
+        response = resource().path("group").path("list")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue(username,
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .get(ClientResponse.class);
+        String entity = response.getEntity(String.class);
+        assertEquals("[]", entity);
+    }
+
+    @Test
+    public void testDeleteGroupOwner () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        // delete marlin from marlin group
+        // dory is a VCA in marlin group
+        ClientResponse response = resource().path("group").path("member")
+                .path("delete").queryParam("memberId", "marlin")
+                .queryParam("groupId", "1")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue("dory",
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .delete(ClientResponse.class);
+
+        String entity = response.getEntity(String.class);
+        //        System.out.println(entity);
+        JsonNode node = JsonUtils.readTree(entity);
+        assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+        assertEquals(StatusCodes.NOT_ALLOWED, node.at("/errors/0/0").asInt());
+        assertEquals("Operation 'delete group owner'is not allowed.",
+                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 testSubscribeMarlinToDoryGroup () throws KustvaktException {
         MultivaluedMap<String, String> form = new MultivaluedMapImpl();
         form.add("groupId", "2");
 
-        ClientResponse response;
-        String entity;
-        response = resource().path("group").path("subscribe")
+        ClientResponse response = resource().path("group").path("subscribe")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION,
@@ -140,71 +298,84 @@
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
         // retrieve marlin group
-        response = resource().path("group").path("list")
-                .header(Attributes.AUTHORIZATION,
-                        handler.createBasicAuthorizationHeaderValue("marlin",
-                                "pass"))
-                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .get(ClientResponse.class);
-        entity = response.getEntity(String.class);
-
-        assertEquals(Status.OK.getStatusCode(), response.getStatus());
-
-        JsonNode node = JsonUtils.readTree(entity);
+        JsonNode node = retrieveMarlinGroups();
+        System.out.println(node);
         assertEquals(2, node.size());
 
-        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());
-                // group members are not allowed to see other members
-                assertEquals(0, 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());
+        // group members are not allowed to see other members
+        assertEquals(0, group.at("/members").size());
+        assertEquals(GroupMemberStatus.ACTIVE.name(),
+                group.at("/userMemberStatus").asText());
 
-        // unsubscribe
-        response = resource().path("group").path("unsubscribe")
+        // unsubscribe marlin from dory group
+        testUnsubscribe(form);
+
+        // invite marlin to dory group to set back the GroupMemberStatus.PENDING
+        testInviteMember();
+    }
+
+    private JsonNode retrieveMarlinGroups () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        ClientResponse response = resource().path("group").path("list")
+                .header(Attributes.AUTHORIZATION,
+                        handler.createBasicAuthorizationHeaderValue("marlin",
+                                "pass"))
+                .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+                .get(ClientResponse.class);
+        String entity = response.getEntity(String.class);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        return JsonUtils.readTree(entity);
+    }
+
+    private void testUnsubscribe (MultivaluedMap<String, String> form)
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        ClientResponse response = resource().path("group").path("unsubscribe")
                 .type(MediaType.APPLICATION_FORM_URLENCODED)
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
                 .header(Attributes.AUTHORIZATION,
                         handler.createBasicAuthorizationHeaderValue("marlin",
                                 "pass"))
                 .entity(form).post(ClientResponse.class);
-        entity = response.getEntity(String.class);
-
-        // retrieve marlin group
-        response = resource().path("group").path("list")
-                .header(Attributes.AUTHORIZATION,
-                        handler.createBasicAuthorizationHeaderValue("marlin",
-                                "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);
+        JsonNode node = retrieveMarlinGroups();
         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");
+    private void testInviteMember () throws UniformInterfaceException,
+            ClientHandlerException, KustvaktException {
+        String[] members = new String[] { "marlin" };
 
         UserGroupJson userGroup = new UserGroupJson();
         userGroup.setMembers(members);
         // dory group
         userGroup.setId(2);
 
-        response = resource().path("group").path("add")
-                .type(MediaType.APPLICATION_JSON)
+        ClientResponse response = resource().path("group").path("member")
+                .path("invite").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);
+
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        // check member
+        JsonNode node = retrieveMarlinGroups();
+        assertEquals(2, node.size());
+        JsonNode group = node.get(1);
+        assertEquals(GroupMemberStatus.PENDING.name(),
+                group.at("/userMemberStatus").asText());
+
     }
 
     // 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 2b68543..6207cb2 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
@@ -9,15 +9,13 @@
 import java.io.InputStreamReader;
 import java.util.List;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 
-import java.util.Set;
-
 import org.apache.http.entity.ContentType;
 import org.eclipse.jetty.http.HttpHeaders;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
@@ -25,8 +23,8 @@
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.ClientResponse.Status;
-import com.sun.jersey.core.util.MultivaluedMapImpl;
 import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
 import com.sun.jersey.spi.container.ContainerRequest;
 
 import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
@@ -243,7 +241,7 @@
         assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
         assertEquals(StatusCodes.AUTHORIZATION_FAILED,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Operation is not permitted for user: guest",
+        assertEquals("Unauthorized operation for user: guest",
                 node.at("/errors/0/1").asText());
 
         checkWWWAuthenticateHeader(response);
@@ -276,7 +274,7 @@
                 .get(ClientResponse.class);
         entity = response.getEntity(String.class);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
-                System.out.println(entity);
+//                System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(2, node.size());
         assertEquals("new vc", node.get(1).get("name").asText());
@@ -402,7 +400,7 @@
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(StatusCodes.AUTHORIZATION_FAILED,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Operation is not permitted for user: guest",
+        assertEquals("Unauthorized operation for user: guest",
                 node.at("/errors/0/1").asText());
 
         checkWWWAuthenticateHeader(response);
@@ -663,10 +661,9 @@
                         handler.createBasicAuthorizationHeaderValue("dory",
                                 "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
                 .get(ClientResponse.class);
         String entity = response.getEntity(String.class);
-        //        System.out.println(entity);
+//                System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(1, node.at("/0/accessId").asInt());
         assertEquals(2, node.at("/0/vcId").asInt());
@@ -684,7 +681,6 @@
                         handler.createBasicAuthorizationHeaderValue("nemo",
                                 "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
                 .get(ClientResponse.class);
         String entity = response.getEntity(String.class);
         assertEquals("[]", entity);
@@ -698,8 +694,6 @@
                                 handler.createBasicAuthorizationHeaderValue(
                                         "VirtualCorpusControllerTest", "pass"))
                         .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                        .header(HttpHeaders.CONTENT_TYPE,
-                                ContentType.APPLICATION_JSON)
                         .get(ClientResponse.class);
         String entity = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(entity);
@@ -717,7 +711,6 @@
                         handler.createBasicAuthorizationHeaderValue("dory",
                                 "pass"))
                 .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
-                .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
                 .get(ClientResponse.class);
         String entity = response.getEntity(String.class);
         //        System.out.println(entity);
@@ -727,7 +720,7 @@
         assertEquals("group VC", node.at("/0/vcName").asText());
         assertEquals(2, node.at("/0/userGroupId").asInt());
         assertEquals("dory group", node.at("/0/userGroupName").asText());
-    }
+    }   
 
 
     @Test
diff --git a/lite/Changes b/lite/Changes
new file mode 100644
index 0000000..2444966
--- /dev/null
+++ b/lite/Changes
@@ -0,0 +1,10 @@
+0.59.9 2018-01-17
+	- renamed light to lite (margaretha)
+	- added Changes file (margaretha)
+0.59.8 2018-01-17 
+	- restructured Kustvakt and created /lite project
+	- removed version from service paths (margaretha)
+	- updated query serialization tests (margaretha)
+	- added statistic service test (margaretha)
+	- updated KustvaktResponseHandler (margaretha)
+	- removed FastJerseyLight and simplified Jersey test configuration (margaretha)	
\ No newline at end of file
diff --git a/lite/pom.xml b/lite/pom.xml
index 7dcb096..3eb4e15 100644
--- a/lite/pom.xml
+++ b/lite/pom.xml
@@ -155,14 +155,7 @@
 		<dependency>
 			<groupId>de.ids_mannheim.korap</groupId>
 			<artifactId>Kustvakt-core</artifactId>
-			<version>0.59.9</version>
-		</dependency>
-		<dependency>
-			<groupId>de.ids_mannheim.korap</groupId>
-			<artifactId>Kustvakt-core</artifactId>
-			<version>0.59.8</version>
-			<type>test-jar</type>
-			<scope>test</scope>
+			<version>0.59.10</version>
 		</dependency>
 		
 		<!-- Spring -->