Merge "Introduced preliminary support for meta data field retrieval"
diff --git a/README.md b/README.md
index a744b66..634aae8 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@
 # Setup
 
 
-Prerequisites: Jdk 1.7, Git, Maven 3, MySQL (optional).
+Prerequisites: Jdk 1.8, Git, Maven 3, MySQL (optional), Postfix (optional).
 
 Clone the latest version of Kustvakt
 <pre>
@@ -147,6 +147,92 @@
 ```full/src/main/resources/db/new-mysql``` and other paths specified in 
 ```jdbc.schemaPath```.
 
+## Enabling Mail
+
+Kustvakt supports email notification, for instance of a invitation to a user-group.
+The mail setting is by default configured for a mail server at localhost post 25. 
+You can setup a mail server for example by using [Postfix](http://www.postfix.org/).
+
+In kustvakt.conf or kustvakt-test.conf, set  
+
+	mail.enabled = true
+	mail.receiver = test@localhost
+	mail.sender = noreply@ids-mannheim.de
+
+
+You can change ```mail.sender``` value to any email address.
+```mail.receiver``` is only used for testing purpose. Change 
+```test``` to any username available in your system, or create an alias for 
+```test@localhost```. 
+
+To view the mailbox, you can use ```mailx``` 
+
+	$ mailx -u test
+	s-nail version v14.8.6.  Type ? for help.
+	"/var/mail/test": 1 messages 0 unread
+	 O  1 noreply@ids-mannhe Wed Feb 21 18:07   30/1227  Invitation to join
+
+
+### Creating email aliases 
+
+	sudo vi /etc/postfix/main.cf
+	
+In main.cf, set
+
+	"virtual_alias_maps = hash:/etc/postfix/alias"
+
+Create alias file:
+
+	sudo vi /etc/postfix/alias
+
+In the alias file, create an alias for ```test@localhost``` to a user in your system
+
+	test@localhost username
+
+Create alias database
+
+	sudo postmap /etc/postfix/alias
+	
+Restart postfix
+
+	sudo /etc/init.d/postfix restart
+
+
+By default, any emails sent to ```test@localhost``` will be available at 
+```/var/mail/username```.
+
+### Customizing SMTP configuration
+
+To connect to an external/remote mail server instead of using local Postfix,
+copy ```full/src/main/resources/properties/mail.properties``` to 
+the ```full/``` folder. Customize the properties in the file according to
+the mail server setup.  
+
+	mail.host = smtp.host.address
+	mail.port = 123
+	mail.connectiontimeout = 3000
+	mail.auth = true
+	mail.starttls.enable = true
+	mail.username = username
+	mail.password = password
+ 
+### Customizing Mail template
+
+Kustvakt uses [Apache Velocity](http://velocity.apache.org/) as the email template engine and searches for templates located at:
+
+```full/src/main/resources/templates```.
+
+For instance, the template for email notification of user-group invitation is 
+
+```full/src/main/resources/templates/notification.vm```
+
+You can change the template according to Velocity Template Language.
+
+In ```kustvakt.conf``` or ```kustvakt-test.conf```, specify which template should be used. 
+	
+	template.group.invitation = notification.vm
+
+
 # Known issues
 Tests are verbose - they do not necessarily imply system errors.
 
diff --git a/core/Changes b/core/Changes
index d1d629e..3b341f8 100644
--- a/core/Changes
+++ b/core/Changes
@@ -1,14 +1,22 @@
-0.59.10 2018-02-01 
+version 0.59.10 
+20/02/2018
 	- updated hibernate and reflection versions (margaretha)
 	- added Changes file (margaretha)
 	- merged BeanConfigBaseTest to BeanConfigTest in /full (margaretha)
 	- added status code for already deleted entry (margaretha)
-	- updated library versions and java environment (margaretha)	
-0.59.9 	2017-11-08
+	- updated library versions and java environment (margaretha)
+	- added status codes (margaretha)
+	- moved validation.properties (margaretha)
+	- fixed unrecognize media-type application/json (margaretha)
+	
+version 0.59.9 	
+08/11/2017
 	- fixed missing exception in JsonUtils (margaretha)
 	- fixed and restructured KustvaktResponseHandler (margaretha)
 	- updated status code in ParameterChecker (margaretha)
-0.59.8 2017-10-24
+	
+version 0.59.8 
+24/10/2017
 	- restructured Kustvakt and created core project (margaretha)
 	- marked loader classes as deprecated (margaretha)
 	- updated Spring version (margaretha)
diff --git a/core/pom.xml b/core/pom.xml
index 2e8728d..ee9b4b0 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -10,7 +10,7 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<spring-framework.version>5.0.3.RELEASE</spring-framework.version>
 		<jersey.version>1.19.4</jersey.version>
-		<jetty.version>8.2.0.v20160908</jetty.version>
+		<jetty.version>9.4.8.v20171121</jetty.version>
 		<hibernate.version>5.1.11.Final</hibernate.version>
 	</properties>
 	<build>
@@ -108,7 +108,7 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-surefire-plugin</artifactId>
-				<version>2.19.1</version>
+				<version>2.20.1</version>
 
 				<configuration>
 					<reuseForks>false</reuseForks>
@@ -358,6 +358,8 @@
 			<artifactId>commons-collections</artifactId>
 			<version>3.2.2</version>
 		</dependency>
+		
+		<!-- jetty -->
 		<dependency>
 			<groupId>org.eclipse.jetty</groupId>
 			<artifactId>jetty-server</artifactId>
@@ -369,6 +371,12 @@
 			<version>${jetty.version}</version>
 		</dependency>
 		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-webapp</artifactId>
+			<version>${jetty.version}</version>
+		</dependency>
+		
+		<dependency>
 			<groupId>asm</groupId>
 			<artifactId>asm</artifactId>
 			<version>3.3.1</version>
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 5b57438..941b5af 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
@@ -79,9 +79,16 @@
     public static final int DB_INSERT_SUCCESSFUL = 505;
     public static final int DB_DELETE_SUCCESSFUL = 506;
     public static final int DB_UPDATE_SUCCESSFUL = 507;
-    
     public static final int DB_ENTRY_EXISTS = 508;
-    public static final int DB_ENTRY_DELETED = 509;
+    
+    
+    // User group and member 
+    public static final int GROUP_MEMBER_EXISTS = 601;
+    public static final int GROUP_MEMBER_DELETED = 602;
+    public static final int GROUP_MEMBER_NOT_FOUND = 603;
+    public static final int INVITATION_EXPIRED = 604;
+    public static final int GROUP_NOT_FOUND = 605;
+    
 
 //    public static final int ARGUMENT_VALIDATION_FAILURE = 700;
     // public static final int ARGUMENT_VALIDATION_FAILURE = 701;
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 8732a21..3cba4a2 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
@@ -24,9 +24,10 @@
 @Data
 public abstract class User implements Serializable {
 
-//    public static final int ADMINISTRATOR_ID = 34349733;
-//    public static final String ADMINISTRATOR_NAME = "admin";
-
+    //EM: add
+    private String email;
+    //EM: finish
+    
     private Integer id;
     // in local its username, in shib it's edupersonPrincipalName
     private String username;
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/KustvaktBaseServer.java b/core/src/main/java/de/ids_mannheim/korap/web/KustvaktBaseServer.java
index 7ad0500..8496d04 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/KustvaktBaseServer.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/KustvaktBaseServer.java
@@ -4,14 +4,14 @@
 
 import org.eclipse.jetty.server.Connector;
 import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.springframework.stereotype.Component;
 import org.springframework.web.context.ContextLoaderListener;
 
 import com.sun.jersey.spi.spring.container.servlet.SpringServlet;
 
-import de.ids_mannheim.korap.config.BeansFactory;
 import de.ids_mannheim.korap.config.KustvaktConfiguration;
 import lombok.Getter;
 import lombok.Setter;
@@ -20,11 +20,14 @@
  * @author hanl
  * @date 01/06/2015
  */
+@Component
 public abstract class KustvaktBaseServer {
-    
+
+    protected static KustvaktConfiguration config;
+
     protected static String rootPackages;
     protected static KustvaktArgs kargs;
-    
+
     public KustvaktBaseServer () {
         KustvaktConfiguration.loadLogger();
     }
@@ -59,38 +62,37 @@
         return kargs;
     }
 
-    protected abstract void setup ();
+    protected void start () {
 
-    protected void start(){
-        KustvaktConfiguration config = BeansFactory.getKustvaktContext().getConfiguration();
-        
-        if (kargs.init){
-            setup();
-        }
-        if (kargs.port == -1){
+        if (kargs.port == -1) {
             kargs.setPort(config.getPort());
         }
-        
+
         Server server = new Server();
-        ServletContextHandler contextHandler = new ServletContextHandler(
-                ServletContextHandler.NO_SESSIONS);
+
+        ServletContextHandler contextHandler =
+                new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
         contextHandler.setContextPath("/");
-        contextHandler.setInitParameter("contextConfigLocation", "classpath:"+kargs.getConfig());
-        
+        contextHandler.setInitParameter("contextConfigLocation",
+                "classpath:" + kargs.getConfig());
+
         ServletContextListener listener = new ContextLoaderListener();
         contextHandler.addEventListener(listener);
 
         ServletHolder servletHolder = new ServletHolder(new SpringServlet());
-        servletHolder.setInitParameter("com.sun.jersey.config.property.packages", 
-                rootPackages);
+        servletHolder.setInitParameter(
+                "com.sun.jersey.config.property.packages", rootPackages);
+        servletHolder.setInitParameter(
+                "com.sun.jersey.api.json.POJOMappingFeature", "true");
         servletHolder.setInitOrder(1);
         contextHandler.addServlet(servletHolder, config.getBaseURL());
-        
-        SocketConnector connector = new SocketConnector();
+
+        ServerConnector connector = new ServerConnector(server);
         connector.setPort(kargs.port);
-        connector.setMaxIdleTime(60000);
-        
+        connector.setIdleTimeout(60000);
+
         server.setHandler(contextHandler);
+
         server.setConnectors(new Connector[] { connector });
         try {
             server.start();
@@ -99,11 +101,11 @@
         catch (Exception e) {
             System.out.println("Server could not be started!");
             System.out.println(e.getMessage());
+            e.printStackTrace();
             System.exit(-1);
-//            e.printStackTrace();
         }
     }
-    
+
     @Setter
     public static class KustvaktArgs {
 
diff --git a/full/src/main/resources/validation.properties b/core/src/main/resources/validation.properties
similarity index 100%
rename from full/src/main/resources/validation.properties
rename to core/src/main/resources/validation.properties
diff --git a/full/Changes b/full/Changes
index b147983..7d83752 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,17 +1,35 @@
-0.59.10	2018-02-01 
+version 0.60 release
+21/02/2018
+	- set up mail settings using localhost port 25 (margaretha)
+	- added mail template in kustvakt configuration (margaretha)
+	- added mail settings to readme (margaretha)
+	- disabled email notification for auto group (margaretha)
+
+version 0.59.10	
+20/02/2018 
 	- 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 add/invite users to group (margaretha)
 	- implemented delete user-group and member tasks (margaretha)
 	- added userMemberStatus in group lists (margaretha)
 	- updated and added SQL test data (margaretha)
 	- added user group related tests (margaretha)
 	- implemented custom configuration for deleting user groups and members (margaretha)
 	- updated library versions and java environment (margaretha)
+	- added expiration time check for member invitation (margaretha)
+	- moved .properties files (margaretha) 
+	- merged changelog file to Changes (margaretha)
+	- updated status codes and error messages to be more detailed (margaretha)
+	- testing mail implementation using embedded jetty jndi (margaretha)
+	- fixed collection rewrite regarding OR operation with other fields (margaretha)
+	- implemented sending mail using spring injection and removed jetty jndi (margaretha)
+	- fixed unrecognized application/json (margaretha)
+	- fixed and updated velocity template (margaretha)
 	
-0.59.9 2018-01-19
+version 0.59.9 
+19/01/2018
 	- restructured basic authentication (margaretha)
 	- fixed AuthenticationException to include authentication scheme (margaretha)
 	- fixed rewrite redundancy in collection rewrite (margaretha)
@@ -21,7 +39,7 @@
 	- 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)
+	- implemented create user-group, subscribe to user-groups, unsubscribe to user-groups tasks(margaretha)
 	- fixed handling JSON mapping exception for missing enums (margaretha)
     - implemented list VC task (margaretha)
     - added KoralQuery in VC lists (margaretha)
@@ -37,8 +55,8 @@
     - removed PredefinedUserGroup.ALL and related codes (margaretha)
     - implemented search for published VC (margaretha)
     
-
-0.59.8 2017-09-21
+version 0.59.8 
+21/09/2017
 	- restructured statistics service (margaretha)
 	- removed deprecated loader codes and tests (margaretha)
 	- removed old Spring java configurations (margaretha)
@@ -54,5 +72,14 @@
 	- 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
+	- fixed collection rewrite bug regarding availability with operation or (margaretha)    
+
+version 0.59.7
+13/10/2016
+    - MOD: updated search to use new siglen (diewald)
+    - MOD: fixed matchinfo retrieval in light service (diewald)
+
+05/05/2015
+    - ADD: rest test suite for user service (hanl)
+    - MOD: setup parameter modification (hanl)
+    - ADD: oauth2 client unique constraint (hanl)
diff --git a/full/pom.xml b/full/pom.xml
index bccc9bf..b1b92c8 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -3,12 +3,13 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>de.ids_mannheim.korap</groupId>
 	<artifactId>Kustvakt-full</artifactId>
-	<version>0.59.10</version>
+	<version>0.60</version>
 	<properties>
 		<java.version>1.8</java.version>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<spring-framework.version>5.0.3.RELEASE</spring-framework.version>
 		<spring-security.version>4.2.3.RELEASE</spring-security.version>
+		<jetty.version>9.4.8.v20171121</jetty.version>
 		<jersey.version>1.19.4</jersey.version>
 		<hibernate.version>5.1.11.Final</hibernate.version>
 	</properties>
@@ -24,6 +25,7 @@
 					<include>**/*.kustvakt</include>
 					<include>**/*.properties</include>
 					<include>**/*.sql</include>
+					<include>**/*.vm</include>
 				</includes>
 			</resource>
 		</resources>
@@ -72,10 +74,9 @@
 					<compilerVersion>${java.version}</compilerVersion>
 					<source>${java.version}</source>
 					<target>${java.version}</target>
-					<!-- <compilerArguments>
-						<processor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</processor>
-						<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
-					</compilerArguments> -->
+					<!-- <compilerArguments> <processor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</processor> 
+						<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> 
+						</compilerArguments> -->
 					<processors>
 						<processor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor</processor>
 						<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
@@ -85,12 +86,12 @@
 			<plugin>
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-surefire-plugin</artifactId>
-				<version>2.19.1</version>
+				<version>2.20.1</version>
 				<configuration>
 					<reuseForks>false</reuseForks>
 					<forkCount>2</forkCount>
 					<threadCount>10</threadCount>
-
+					<argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
 					<excludes>
 						<!-- <exclude>de/ids_mannheim/korap/suites/*.java</exclude> -->
 						<!-- <exclude>de/ids_mannheim/korap/dao/*.java</exclude> -->
@@ -183,7 +184,7 @@
 			<exclusions>
 				<exclusion>
 					<groupId>org.javassist</groupId>
-		   			<artifactId>javassist</artifactId>
+					<artifactId>javassist</artifactId>
 				</exclusion>
 			</exclusions>
 		</dependency>
@@ -194,9 +195,14 @@
 			<scope>provided</scope>
 		</dependency>
 		<dependency>
-		    <groupId>org.javassist</groupId>
-		    <artifactId>javassist</artifactId>
-		    <version>3.22.0-GA</version>
+			<groupId>org.hibernate</groupId>
+			<artifactId>hibernate-java8</artifactId>
+			<version>${hibernate.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.javassist</groupId>
+			<artifactId>javassist</artifactId>
+			<version>3.22.0-GA</version>
 		</dependency>
 
 		<!-- MySql -->
@@ -219,6 +225,11 @@
 			<version>${jersey.version}</version>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+		    <groupId>com.sun.jersey</groupId>
+		    <artifactId>jersey-json</artifactId>
+		    <version>${jersey.version}</version>
+		</dependency>
 
 		<!-- Spring -->
 		<dependency>
@@ -232,14 +243,55 @@
 			<version>${spring-framework.version}</version>
 		</dependency>
 		<dependency>
-		    <groupId>org.springframework.security</groupId>
-		    <artifactId>spring-security-core</artifactId>
-		    <version>${spring-security.version}</version>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-core</artifactId>
+			<version>${spring-security.version}</version>
 		</dependency>
 		<dependency>
-		    <groupId>org.springframework.security</groupId>
-		    <artifactId>spring-security-web</artifactId>
-		    <version>${spring-security.version}</version>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-web</artifactId>
+			<version>${spring-security.version}</version>
+		</dependency>
+
+		<!-- jetty -->
+		<!-- <dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-jndi</artifactId>
+			<version>${jetty.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.eclipse.jetty</groupId>
+			<artifactId>jetty-plus</artifactId>
+			<version>${jetty.version}</version>
+		</dependency> -->
+
+		<!-- velocity -->
+		<dependency>
+			<groupId>org.apache.velocity</groupId>
+			<artifactId>velocity-engine-core</artifactId>
+			<version>2.0</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.velocity</groupId>
+			<artifactId>velocity-tools</artifactId>
+			<version>2.0</version>
+		</dependency>
+		<!-- mail -->
+		<dependency>
+			<groupId>com.sun.mail</groupId>
+			<artifactId>javax.mail</artifactId>
+			<version>1.6.0</version>
+		</dependency>
+		<dependency>
+			<groupId>javax.activation</groupId>
+			<artifactId>activation</artifactId>
+			<version>1.1.1</version>
+		</dependency>
+		
+		<dependency>
+		    <groupId>javax.servlet</groupId>
+		    <artifactId>javax.servlet-api</artifactId>
+		    <version>4.0.0</version>
 		</dependency>
 
 		<!-- Flyway -->
@@ -249,4 +301,5 @@
 			<version>4.0</version>
 		</dependency>
 	</dependencies>
+
 </project>
\ No newline at end of file
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 46649eb..0015e89 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
@@ -139,6 +139,8 @@
 		//EM:copied from EntityDao
 		KorAPUser user = new KorAPUser(); // oder eigentlich new DemoUser oder new DefaultUser.
         user.setUsername(username);
+        // get user data
+        user.setEmail(config.getTestEmail());
         return user;
 //		return entHandler.getAccount(username);
 	}
@@ -201,7 +203,6 @@
 
 	@Override
 	public void setAccessAndLocation(User user, HttpHeaders headers) {
-		Boolean DEBUG_LOG = true;
 		MultivaluedMap<String, String> headerMap = headers.getRequestHeaders();
 		Location location = Location.EXTERN;
 		CorpusAccess corpusAccess = CorpusAccess.FREE;
@@ -210,14 +211,13 @@
 	    {
 	    	// to be absolutely sure:
 	    	user.setCorpusAccess(User.CorpusAccess.FREE);
-	    	if( DEBUG_LOG == true )
-	    		System.out.printf("setAccessAndLocation: DemoUser: location=%s, access=%s.\n", user.locationtoString(), user.accesstoString());
+	    	jlog.debug("setAccessAndLocation: DemoUser: location=%s, access=%s.\n", user.locationtoString(), user.accesstoString());
 	     	return;
 	    }
 		
-		if (headerMap != null && headerMap.containsKey(org.eclipse.jetty.http.HttpHeaders.X_FORWARDED_FOR)) {
+		if (headerMap != null && headerMap.containsKey(com.google.common.net.HttpHeaders.X_FORWARDED_FOR)) {
 
-			String[] vals = headerMap.getFirst(org.eclipse.jetty.http.HttpHeaders.X_FORWARDED_FOR).split(",");
+			String[] vals = headerMap.getFirst(com.google.common.net.HttpHeaders.X_FORWARDED_FOR).split(",");
 			String clientAddress = vals[0];
 
 			try {
@@ -243,8 +243,8 @@
 
 			user.setLocation(location);
 			user.setCorpusAccess(corpusAccess);
-	    	if( DEBUG_LOG == true )
-	    		System.out.printf("setAccessAndLocation: KorAPUser: location=%s, access=%s.\n", user.locationtoString(), user.accesstoString());
+	    	
+	    	jlog.debug("setAccessAndLocation: KorAPUser: location=%s, access=%s.\n", user.locationtoString(), user.accesstoString());
 
 		}
 	} // getAccess
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
index 7b9c48c..201fc4b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
@@ -14,7 +14,13 @@
  */
 
 public class FullConfiguration extends KustvaktConfiguration {
+    // mail configuration
+    private boolean isMailEnabled;
+    private String testEmail;
+    private String noReply;
 
+    private String groupInvitationTemplate;
+    
     private String ldapConfig;
 
     private String freeOnlyRegex;
@@ -49,12 +55,26 @@
         // EM: pattern for matching availability in Krill matches
         setLicensePatterns(properties);
         setDeleteConfiguration(properties);
+        setMailConfiguration(properties);
         ldapConfig = properties.getProperty("ldap.config");
+
+    }
+
+    private void setMailConfiguration (Properties properties) {
+        setMailEnabled(Boolean.valueOf(properties.getProperty("mail.enabled", "false")));
+        if (isMailEnabled){
+            // other properties must be set in the kustvakt.conf
+            setTestEmail(properties.getProperty("mail.receiver"));
+            setNoReply(properties.getProperty("mail.sender"));
+            setGroupInvitationTemplate(properties.getProperty("template.group.invitation"));
+        }
     }
 
     private void setDeleteConfiguration (Properties properties) {
-        setSoftDeleteGroup(parseDeleteConfig(properties.getProperty("delete.group", "")));
-        setSoftDeleteAutoGroup(parseDeleteConfig(properties.getProperty("delete.auto.group", "")));
+        setSoftDeleteGroup(
+                parseDeleteConfig(properties.getProperty("delete.group", "")));
+        setSoftDeleteAutoGroup(parseDeleteConfig(
+                properties.getProperty("delete.auto.group", "")));
         setSoftDeleteGroupMember(parseDeleteConfig(
                 properties.getProperty("delete.group.member", "")));
     }
@@ -217,4 +237,36 @@
         this.isSoftDeleteAutoGroup = isSoftDeleteAutoGroup;
     }
 
+    public String getTestEmail () {
+        return testEmail;
+    }
+
+    public void setTestEmail (String testEmail) {
+        this.testEmail = testEmail;
+    }
+
+    public boolean isMailEnabled () {
+        return isMailEnabled;
+    }
+
+    public void setMailEnabled (boolean isMailEnabled) {
+        this.isMailEnabled = isMailEnabled;
+    }
+
+    public String getNoReply () {
+        return noReply;
+    }
+
+    public void setNoReply (String noReply) {
+        this.noReply = noReply;
+    }
+
+    public String getGroupInvitationTemplate () {
+        return groupInvitationTemplate;
+    }
+
+    public void setGroupInvitationTemplate (String groupInvitationTemplate) {
+        this.groupInvitationTemplate = groupInvitationTemplate;
+    }
+
 }
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 6885f3d..e960ee1 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
@@ -94,7 +94,7 @@
             entityManager.merge(group);
         }
         else {
-            if (!entityManager.contains(group)){
+            if (!entityManager.contains(group)) {
                 group = entityManager.merge(group);
             }
             entityManager.remove(group);
@@ -141,9 +141,8 @@
             return (UserGroup) q.getSingleResult();
         }
         catch (NoResultException e) {
-            throw new KustvaktException(StatusCodes.NO_RESULT_FOUND,
-                    "No result found for query: retrieve group by id "
-                            + groupId,
+            throw new KustvaktException(StatusCodes.GROUP_NOT_FOUND,
+                    "Group with id " + groupId + " is not found",
                     String.valueOf(groupId), e);
         }
     }
@@ -174,8 +173,8 @@
                         userId),
                 criteriaBuilder.notEqual(members.get(UserGroupMember_.status),
                         GroupMemberStatus.DELETED));
-//                criteriaBuilder.equal(members.get(UserGroupMember_.status),
-//                        GroupMemberStatus.ACTIVE));
+        //                criteriaBuilder.equal(members.get(UserGroupMember_.status),
+        //                        GroupMemberStatus.ACTIVE));
 
 
         query.select(root);
@@ -220,7 +219,8 @@
         }
     }
 
-    public UserGroup retrieveHiddenGroupByVC (int vcId) throws KustvaktException {
+    public UserGroup retrieveHiddenGroupByVC (int vcId)
+            throws KustvaktException {
         ParameterChecker.checkIntegerValue(vcId, "vcId");
 
         CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
@@ -237,7 +237,7 @@
                 criteriaBuilder.equal(root.get(UserGroup_.status),
                         UserGroupStatus.HIDDEN),
                 criteriaBuilder.equal(vc.get(VirtualCorpus_.id), vcId));
-        
+
         query.select(root);
         query.where(p);
         Query q = entityManager.createQuery(query);
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 9803461..d2ffe28 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
@@ -36,45 +36,33 @@
         entityManager.persist(member);
     }
 
-    public void addMembers (List<UserGroupMember> members)
-            throws KustvaktException {
-        ParameterChecker.checkObjectValue(members, "List<UserGroupMember>");
+    //    @Deprecated
+    //    public void addMembers (List<UserGroupMember> members)
+    //            throws KustvaktException {
+    //        ParameterChecker.checkObjectValue(members, "List<UserGroupMember>");
+    //
+    //        for (UserGroupMember member : members) {
+    //            addMember(member);
+    //        }
+    //    }
 
-        for (UserGroupMember member : members) {
-            addMember(member);
-        }
+    public void updateMember (UserGroupMember member) throws KustvaktException {
+        ParameterChecker.checkObjectValue(member, "UserGroupMember");
+        entityManager.merge(member);
     }
 
-    public void approveMember (String userId, int groupId)
-            throws KustvaktException {
-        ParameterChecker.checkStringValue(userId, "userId");
-        ParameterChecker.checkIntegerValue(groupId, "groupId");
-
-        UserGroupMember member = retrieveMemberById(userId, groupId);
-        if (member.getStatus().equals(GroupMemberStatus.DELETED)) {
-            throw new KustvaktException(StatusCodes.NOTHING_CHANGED, "Username "
-                    + userId + " had been deleted in group " + groupId, userId);
-        }
-
-        member.setStatus(GroupMemberStatus.ACTIVE);
-        entityManager.persist(member);
-    }
-
-    public void deleteMember (String userId, int groupId, String deletedBy,
+    public void deleteMember (UserGroupMember member, String deletedBy,
             boolean isSoftDelete) throws KustvaktException {
-        ParameterChecker.checkStringValue(userId, "userId");
-        ParameterChecker.checkIntegerValue(groupId, "groupId");
+        ParameterChecker.checkObjectValue(member, "UserGroupMember");
+        ParameterChecker.checkStringValue(deletedBy, "deletedBy");
 
-        UserGroupMember member = retrieveMemberById(userId, groupId);
-        GroupMemberStatus status = member.getStatus();
-        if (isSoftDelete && status.equals(GroupMemberStatus.DELETED)) {
-            throw new KustvaktException(StatusCodes.DB_ENTRY_DELETED,
-                    userId + " has already been deleted from the group.",
-                    userId);
+        if (!entityManager.contains(member)) {
+            member = entityManager.merge(member);
         }
 
         if (isSoftDelete) {
             member.setStatus(GroupMemberStatus.DELETED);
+            member.setDeletedBy(deletedBy);
             entityManager.persist(member);
         }
         else {
@@ -107,9 +95,8 @@
             return (UserGroupMember) q.getSingleResult();
         }
         catch (NoResultException e) {
-            throw new KustvaktException(StatusCodes.NO_RESULT_FOUND,
-                    "Username " + userId + " is not found in group " + groupId,
-                    userId);
+            throw new KustvaktException(StatusCodes.GROUP_MEMBER_NOT_FOUND,
+                    userId + " is not found in the group", userId);
         }
 
     }
diff --git a/full/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java b/full/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java
index 6e7f874..f148959 100644
--- a/full/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java
+++ b/full/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java
@@ -1,5 +1,7 @@
 package de.ids_mannheim.korap.entity;
 
+import java.time.ZonedDateTime;
+import java.util.Date;
 import java.util.List;
 
 import javax.persistence.Column;
@@ -47,6 +49,10 @@
     private String createdBy;
     @Column(name = "deleted_by")
     private String deletedBy;
+    
+    // auto update in the database
+    @Column(name = "status_date")
+    private ZonedDateTime statusDate;
 
     @Enumerated(EnumType.STRING)
     private GroupMemberStatus status;
diff --git a/full/src/main/java/de/ids_mannheim/korap/rewrite/CollectionRewrite.java b/full/src/main/java/de/ids_mannheim/korap/rewrite/CollectionRewrite.java
index 7f7370a..1ab70a3 100644
--- a/full/src/main/java/de/ids_mannheim/korap/rewrite/CollectionRewrite.java
+++ b/full/src/main/java/de/ids_mannheim/korap/rewrite/CollectionRewrite.java
@@ -81,9 +81,20 @@
             }
             else {
                 for (int i = 0; i < operands.size(); i++) {
-                    updatedAvailabilities = checkAvailability(operands.get(i),
-                            originalAvailabilities, updatedAvailabilities,
-                            true);
+                    node = operands.get(i);
+                    if (node.has("key") && !node.at("/key").asText()
+                            .equals("availability")) {
+                        jlog.debug("RESET availabilities 1, key="
+                                + node.at("/key").asText());
+                        updatedAvailabilities.clear();
+                        updatedAvailabilities.addAll(originalAvailabilities);
+                        break;
+                    }
+                    else {
+                        updatedAvailabilities = checkAvailability(
+                                operands.get(i), originalAvailabilities,
+                                updatedAvailabilities, true);
+                    }
                 }
             }
         }
@@ -91,13 +102,14 @@
                 && node.at("/key").asText().equals("availability")) {
             String queryAvailability = node.at("/value").asText();
             String matchOp = node.at("/match").asText();
+
             if (originalAvailabilities.contains(queryAvailability)
                     && matchOp.equals(KoralMatchOperator.EQUALS.toString())) {
                 jlog.debug("REMOVE " + queryAvailability);
                 updatedAvailabilities.remove(queryAvailability);
             }
             else if (isOperationOr) {
-                jlog.debug("RESET availabilities");
+                jlog.debug("RESET availabilities 2");
                 updatedAvailabilities.clear();
                 updatedAvailabilities.addAll(originalAvailabilities);
                 return updatedAvailabilities;
@@ -110,7 +122,7 @@
     public JsonNode rewriteQuery (KoralNode node, KustvaktConfiguration config,
             User user) throws KustvaktException {
         JsonNode jsonNode = node.rawNode();
-        
+
         FullConfiguration fullConfig = (FullConfiguration) config;
 
         List<String> userAvailabilities = new ArrayList<String>();
@@ -145,7 +157,7 @@
                     avalabilityCopy, userAvailabilities, false);
             if (!userAvailabilities.isEmpty()) {
                 builder.with(buildAvailability(avalabilityCopy));
-                jlog.debug("corpus query: " +builder.toString());
+                jlog.debug("corpus query: " + builder.toString());
                 builder.setBaseQuery(builder.toJSON());
                 rewrittesNode = builder.mergeWith(jsonNode).at("/collection");
                 node.set("collection", rewrittesNode, identifier);
@@ -153,7 +165,7 @@
         }
         else {
             builder.with(buildAvailability(userAvailabilities));
-            jlog.debug("corpus query: " +builder.toString());
+            jlog.debug("corpus query: " + builder.toString());
             rewrittesNode =
                     JsonUtils.readTree(builder.toJSON()).at("/collection");
             node.set("collection", rewrittesNode, identifier);
@@ -169,27 +181,28 @@
         for (int i = 0; i < userAvailabilities.size(); i++) {
             parseAvailability(sb, userAvailabilities.get(i), "|");
         }
-        String availabilities = sb.toString(); 
-        return availabilities.substring(0, availabilities.length()-3);
+        String availabilities = sb.toString();
+        return availabilities.substring(0, availabilities.length() - 3);
     }
-    
-    private void parseAvailability (StringBuilder sb, String availability, String operator) {
+
+    private void parseAvailability (StringBuilder sb, String availability,
+            String operator) {
         String uaArr[] = null;
-        if (availability.contains("|")){
+        if (availability.contains("|")) {
             uaArr = availability.split("\\|");
-            for (int j=0; j < uaArr.length; j++){
+            for (int j = 0; j < uaArr.length; j++) {
                 parseAvailability(sb, uaArr[j].trim(), "|");
             }
         }
         // EM: not supported
-//        else if (availability.contains("&")){
-//            uaArr = availability.split("&");
-//            for (int j=0; j < uaArr.length -1; j++){
-//                parseAvailability(sb, uaArr[j], "&");
-//            }
-//            parseAvailability(sb, uaArr[uaArr.length-1], "|");
-//        } 
-        else{
+        //        else if (availability.contains("&")){
+        //            uaArr = availability.split("&");
+        //            for (int j=0; j < uaArr.length -1; j++){
+        //                parseAvailability(sb, uaArr[j], "&");
+        //            }
+        //            parseAvailability(sb, uaArr[uaArr.length-1], "|");
+        //        } 
+        else {
             sb.append("availability=/");
             sb.append(availability);
             sb.append("/ ");
@@ -198,6 +211,6 @@
         }
 
     }
-    
+
 }
 
diff --git a/full/src/main/java/de/ids_mannheim/korap/server/KustvaktServer.java b/full/src/main/java/de/ids_mannheim/korap/server/KustvaktServer.java
index 4b3a6d6..b938904 100644
--- a/full/src/main/java/de/ids_mannheim/korap/server/KustvaktServer.java
+++ b/full/src/main/java/de/ids_mannheim/korap/server/KustvaktServer.java
@@ -1,6 +1,11 @@
 package de.ids_mannheim.korap.server;
 
-import de.ids_mannheim.korap.config.BeansFactory;
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.URL;
+import java.util.Properties;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.web.KustvaktBaseServer;
 
 /**
@@ -11,17 +16,35 @@
  */
 public class KustvaktServer extends KustvaktBaseServer {
 
+    private static FullConfiguration fullConfig;
+    
     public static final String API_VERSION = "v0.1";
     
     public static void main (String[] args) throws Exception {
         KustvaktServer server = new KustvaktServer();
         kargs = server.readAttributes(args);
+        
+        File f = new File("kustvakt.conf");
+        if (!f.exists()){
+            URL url = KustvaktServer.class.getResource("kustvakt.conf");
+            if (url!=null){
+                f = new File(url.toURI());
+            }
+        }
+        
+        Properties properties = new Properties();
+        FileInputStream in = new FileInputStream(f);
+        properties.load(in);
+        in.close();
+        fullConfig = new FullConfiguration(properties);
+        config = fullConfig;
 
-        if (kargs.getConfig() != null)
-            BeansFactory.loadFileContext(kargs.getConfig());
-        else{
+        if (kargs.getConfig() == null){
+//            BeansFactory.loadFileContext(kargs.getConfig());
+//        }
+//        else {
             kargs.setConfig("default-config.xml");
-            BeansFactory.loadClasspathContext("default-config.xml");
+//            BeansFactory.loadClasspathContext("default-config.xml");
         }
         kargs.setRootPackages(new String[] { "de.ids_mannheim.korap.web.utils",
                 "de.ids_mannheim.korap.web.service.full" });
@@ -29,54 +52,4 @@
                 + "de.ids_mannheim.korap.web.service.full";
         server.start();
     }
-
-    @Override
-    protected void setup () {
-//        Set<Class<? extends BootableBeanInterface>> set = KustvaktClassLoader
-//                .loadSubTypes(BootableBeanInterface.class);
-//
-//        ContextHolder context = BeansFactory.getKustvaktContext();
-//        if (context == null)
-//            throw new RuntimeException("Beans could not be loaded!");
-//
-//        List<BootableBeanInterface> list = new ArrayList<>(set.size());
-//        for (Class cl : set) {
-//            BootableBeanInterface iface;
-//            
-//            try {
-//                iface = (BootableBeanInterface) cl.newInstance();
-//                if (iface instanceof CollectionLoader){
-//                	continue;
-//                }
-//                list.add(iface);
-//            }
-//            catch (InstantiationException | IllegalAccessException e) {
-//                continue;
-//            }
-//        }
-//        System.out.println("Found boot loading interfaces: " + list);
-//
-//        while (!list.isEmpty()) {
-//            loop: for (BootableBeanInterface iface : new ArrayList<>(list)) {
-//                try {
-//                    for (Class dep : iface.getDependencies()) {
-//                        if (set.contains(dep))
-//                            continue loop;
-//                    }
-//                    iface.load(context);
-//                    list.remove(iface);
-//                    set.remove(iface.getClass());
-//                    System.out.println("Done with interface "
-//                            + iface.getClass().getSimpleName());
-//                }
-//                catch (KustvaktException e) {
-//                    // don't do anything!
-//                    System.out.println("An error occurred in class "
-//                            + iface.getClass().getSimpleName() + "!\n");
-//                    e.printStackTrace();
-//                    System.exit(-1);
-//                }
-//            }
-//        }
-    }
 }
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/MailAuthenticator.java b/full/src/main/java/de/ids_mannheim/korap/service/MailAuthenticator.java
new file mode 100644
index 0000000..28e7660
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/service/MailAuthenticator.java
@@ -0,0 +1,20 @@
+package de.ids_mannheim.korap.service;
+
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+
+public class MailAuthenticator extends Authenticator {
+
+    private PasswordAuthentication passwordAuthentication;
+
+    public MailAuthenticator (String username, String password) {
+        passwordAuthentication = new PasswordAuthentication(username, password);
+
+    }
+
+    @Override
+    protected PasswordAuthentication getPasswordAuthentication () {
+        return passwordAuthentication;
+    }
+
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/MailService.java b/full/src/main/java/de/ids_mannheim/korap/service/MailService.java
new file mode 100644
index 0000000..866a0a9
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/service/MailService.java
@@ -0,0 +1,76 @@
+package de.ids_mannheim.korap.service;
+
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.mail.javamail.MimeMessagePreparator;
+import org.springframework.stereotype.Service;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.user.User;
+
+@Service
+public class MailService {
+
+    private static Logger jlog = LoggerFactory.getLogger(MailService.class);
+
+    @Autowired
+    private AuthenticationManagerIface authManager;
+    @Autowired
+    private JavaMailSender mailSender;
+    @Autowired
+    private VelocityEngine velocityEngine;
+    @Autowired
+    private FullConfiguration config;
+
+    public void sendMemberInvitationNotification (String inviteeName,
+            String groupName, String inviter) {
+
+        MimeMessagePreparator preparator = new MimeMessagePreparator() {
+
+            public void prepare (MimeMessage mimeMessage) throws Exception {
+
+                User invitee = authManager.getUser(inviteeName);
+
+                MimeMessageHelper message = new MimeMessageHelper(mimeMessage);
+                message.setTo(new InternetAddress(invitee.getEmail()));
+                message.setFrom(config.getNoReply());
+                message.setSubject("Invitation to join " + groupName);
+                message.setText(prepareGroupInvitationText(inviteeName,
+                        groupName, inviter), true);
+            }
+
+        };
+        mailSender.send(preparator);
+    }
+
+    private String prepareGroupInvitationText (String username,
+            String groupName, String inviter) {
+        Context context = new VelocityContext();
+        context.put("username", username);
+        context.put("group", groupName);
+        context.put("inviter", inviter);
+
+        StringWriter stringWriter = new StringWriter();
+
+        velocityEngine.mergeTemplate(
+                "templates/" + config.getGroupInvitationTemplate(),
+                StandardCharsets.UTF_8.name(), context, stringWriter);
+
+        String message = stringWriter.toString();
+        jlog.debug(message);
+        return message;
+    }
+}
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 15f6052..9c3e245 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,9 +1,12 @@
 package de.ids_mannheim.korap.service;
 
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -37,6 +40,8 @@
 @Service
 public class UserGroupService {
 
+    private static Logger jlog =
+            LoggerFactory.getLogger(UserGroupService.class);
     @Autowired
     private UserGroupDao userGroupDao;
     @Autowired
@@ -49,6 +54,8 @@
     private AuthenticationManagerIface authManager;
     @Autowired
     private FullConfiguration config;
+    @Autowired
+    private MailService mailService;
 
     private static List<Role> memberRoles;
 
@@ -168,7 +175,7 @@
                 // skip owner, already added while creating group.
                 continue;
             }
-            addGroupMember(memberId, userGroup, createdBy,
+            inviteGroupMember(memberId, userGroup, createdBy,
                     GroupMemberStatus.PENDING);
         }
     }
@@ -199,7 +206,8 @@
     public void deleteAutoHiddenGroup (int groupId, String deletedBy)
             throws KustvaktException {
         // default hard delete
-        userGroupDao.deleteGroup(groupId, deletedBy, config.isSoftDeleteAutoGroup());
+        userGroupDao.deleteGroup(groupId, deletedBy,
+                config.isSoftDeleteAutoGroup());
     }
 
     /** Adds a user to the specified usergroup. If the username with 
@@ -217,7 +225,7 @@
      * @param status the status of the membership
      * @throws KustvaktException
      */
-    public void addGroupMember (String username, UserGroup userGroup,
+    public void inviteGroupMember (String username, UserGroup userGroup,
             String createdBy, GroupMemberStatus status)
             throws KustvaktException {
 
@@ -225,9 +233,10 @@
         ParameterChecker.checkIntegerValue(groupId, "userGroupId");
 
         if (memberExists(username, groupId, status)) {
-            throw new KustvaktException(StatusCodes.DB_ENTRY_EXISTS,
+            throw new KustvaktException(StatusCodes.GROUP_MEMBER_EXISTS,
                     "Username " + username + " with status " + status
-                            + " exists in user-group " + userGroup.getName(),
+                            + " exists in the user-group "
+                            + userGroup.getName(),
                     username, status.name(), userGroup.getName());
         }
 
@@ -239,8 +248,11 @@
         member.setRoles(memberRoles);
         member.setStatus(status);
         member.setUserId(username);
-
         groupMemberDao.addMember(member);
+
+        if (config.isMailEnabled() && userGroup.getStatus() != UserGroupStatus.HIDDEN) {
+            mailService.sendMemberInvitationNotification(username,userGroup.getName(), createdBy);
+        }
     }
 
     private boolean memberExists (String username, int groupId,
@@ -261,13 +273,13 @@
         }
         else if (existingStatus.equals(GroupMemberStatus.DELETED)) {
             // hard delete, not customizable
-            groupMemberDao.deleteMember(username, groupId, "system", false);
+            deleteMember(username, groupId, "system", false);
         }
 
         return false;
     }
 
-    public void addUsersToGroup (UserGroupJson group, String username)
+    public void inviteGroupMembers (UserGroupJson group, String inviter)
             throws KustvaktException {
         int groupId = group.getId();
         String[] members = group.getMembers();
@@ -275,16 +287,16 @@
         ParameterChecker.checkObjectValue(members, "members");
 
         UserGroup userGroup = retrieveUserGroupById(groupId);
-        User user = authManager.getUser(username);
-        if (isUserGroupAdmin(username, userGroup) || user.isSystemAdmin()) {
+        User user = authManager.getUser(inviter);
+        if (isUserGroupAdmin(inviter, userGroup) || user.isSystemAdmin()) {
             for (String memberName : members) {
-                addGroupMember(memberName, userGroup, username,
+                inviteGroupMember(memberName, userGroup, inviter,
                         GroupMemberStatus.PENDING);
             }
         }
         else {
             throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
-                    "Unauthorized operation for user: " + username, username);
+                    "Unauthorized operation for user: " + inviter, inviter);
         }
     }
 
@@ -307,26 +319,46 @@
      * @param username the username of the group member
      * @throws KustvaktException
      */
-    public void subscribe (int groupId, String username)
+    public void acceptInvitation (int groupId, String username)
             throws KustvaktException {
-        groupMemberDao.approveMember(username, groupId);
+
+        ParameterChecker.checkStringValue(username, "userId");
+        ParameterChecker.checkIntegerValue(groupId, "groupId");
+
+        UserGroup group = userGroupDao.retrieveGroupById(groupId);
+
+        UserGroupMember member =
+                groupMemberDao.retrieveMemberById(username, groupId);
+        GroupMemberStatus status = member.getStatus();
+        if (status.equals(GroupMemberStatus.DELETED)) {
+            throw new KustvaktException(StatusCodes.GROUP_MEMBER_DELETED,
+                    username + " has already been deleted from the group "
+                            + group.getName(),
+                    username, group.getName());
+        }
+        else if (member.getStatus().equals(GroupMemberStatus.ACTIVE)) {
+            throw new KustvaktException(StatusCodes.GROUP_MEMBER_EXISTS,
+                    "Username " + username + " with status " + status
+                            + " exists in the user-group " + group.getName(),
+                    username, status.name(), group.getName());
+        }
+        // status pending
+        else {
+            jlog.debug("status: " + member.getStatusDate());
+            ZonedDateTime expiration = member.getStatusDate().plusMinutes(30);
+            ZonedDateTime now = ZonedDateTime.now();
+            jlog.debug("expiration: " + expiration + ", now: " + now);
+
+            if (expiration.isAfter(now)) {
+                member.setStatus(GroupMemberStatus.ACTIVE);
+                groupMemberDao.updateMember(member);
+            }
+            else {
+                throw new KustvaktException(StatusCodes.INVITATION_EXPIRED);
+            }
+        }
     }
 
-
-    /** Updates the {@link GroupMemberStatus} of a member to 
-     * {@link GroupMemberStatus#DELETED}
-     * 
-     * @param groupId groupId
-     * @param username member's username
-     * @throws KustvaktException
-     */
-    public void unsubscribe (int groupId, String username)
-            throws KustvaktException {
-        groupMemberDao.deleteMember(username, groupId, username,
-                config.isSoftDeleteGroupMember());
-    }
-
-
     public boolean isMember (String username, UserGroup userGroup)
             throws KustvaktException {
         List<UserGroupMember> members =
@@ -349,10 +381,11 @@
                     "Operation " + "'delete group owner'" + "is not allowed.",
                     "delete group owner");
         }
-        else if (isUserGroupAdmin(deletedBy, userGroup)
+        else if (memberId.equals(deletedBy)
+                || isUserGroupAdmin(deletedBy, userGroup)
                 || user.isSystemAdmin()) {
             // soft delete
-            groupMemberDao.deleteMember(memberId, groupId, deletedBy,
+            deleteMember(memberId, groupId, deletedBy,
                     config.isSoftDeleteGroupMember());
         }
         else {
@@ -361,4 +394,31 @@
         }
     }
 
+    /** Updates the {@link GroupMemberStatus} of a member to 
+     * {@link GroupMemberStatus#DELETED}
+     * 
+     * @param userId user to be deleted
+     * @param groupId user-group id
+     * @param deletedBy user that issue the delete 
+     * @param isSoftDelete true if database entry is to be deleted 
+     * permanently, false otherwise
+     * @throws KustvaktException
+     */
+    private void deleteMember (String username, int groupId, String deletedBy,
+            boolean isSoftDelete) throws KustvaktException {
+
+        UserGroup group = userGroupDao.retrieveGroupById(groupId);
+
+        UserGroupMember member =
+                groupMemberDao.retrieveMemberById(username, groupId);
+        GroupMemberStatus status = member.getStatus();
+        if (isSoftDelete && status.equals(GroupMemberStatus.DELETED)) {
+            throw new KustvaktException(StatusCodes.GROUP_MEMBER_DELETED,
+                    username + " has already been deleted from the group "
+                            + group.getName(),
+                    username, group.getName());
+        }
+
+        groupMemberDao.deleteMember(member, deletedBy, isSoftDelete);
+    }
 }
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 b4e7fbc..4e0214b 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
@@ -395,7 +395,7 @@
                         userGroupService.retrieveHiddenGroup(vcId);
                 //                if (!userGroupService.isMember(username, userGroup)) {
                 try {
-                    userGroupService.addGroupMember(username, userGroup,
+                    userGroupService.inviteGroupMember(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 ad92c84..9e15d33 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
@@ -59,6 +59,11 @@
     @Autowired
     private UserGroupService service;
 
+    /** Returns all user-groups wherein a user is an active or pending member.
+     * 
+     * @param securityContext
+     * @return a list of user-groups
+     */
     @GET
     @Path("list")
     public Response getUserGroup (@Context SecurityContext securityContext) {
@@ -120,8 +125,7 @@
     @DELETE
     @Path("delete")
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response deleteUserGroup (
-            @Context SecurityContext securityContext,
+    public Response deleteUserGroup (@Context SecurityContext securityContext,
             @QueryParam("groupId") int groupId) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
@@ -133,7 +137,7 @@
             throw responseHandler.throwit(e);
         }
     }
-    
+
     /** Group owner cannot be deleted.
      * 
      * @param securityContext
@@ -162,12 +166,12 @@
     @POST
     @Path("member/invite")
     @Consumes(MediaType.APPLICATION_JSON)
-    public Response addUserToGroup (@Context SecurityContext securityContext,
+    public Response inviteGroupMembers (@Context SecurityContext securityContext,
             UserGroupJson group) {
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
-            service.addUsersToGroup(group, context.getUsername());
+            service.inviteGroupMembers(group, context.getUsername());
             return Response.ok().build();
         }
         catch (KustvaktException e) {
@@ -183,7 +187,7 @@
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
-            service.subscribe(groupId, context.getUsername());
+            service.acceptInvitation(groupId, context.getUsername());
             return Response.ok().build();
         }
         catch (KustvaktException e) {
@@ -200,7 +204,8 @@
         TokenContext context =
                 (TokenContext) securityContext.getUserPrincipal();
         try {
-            service.unsubscribe(groupId, context.getUsername());
+            service.deleteGroupMember(context.getUsername(), groupId,
+                    context.getUsername());
             return Response.ok().build();
         }
         catch (KustvaktException e) {
diff --git a/full/src/main/resources/changelog b/full/src/main/resources/changelog
deleted file mode 100644
index e7e868c..0000000
--- a/full/src/main/resources/changelog
+++ /dev/null
@@ -1,10 +0,0 @@
-=== CHANGELOG FILE ===
-
-13/10/2016
-    - MOD: updated search to use new siglen (diewald)
-    - MOD: fixed matchinfo retrieval in light service (diewald)
-
-05/05/2015
-    - ADD: rest test suite for user service (hanl)
-    - MOD: setup parameter modification (hanl)
-    - ADD: oauth2 client unique constraint (hanl)
diff --git a/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
index a8df972..1fe0f26 100644
--- a/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/new-mysql/V1.1__create_virtual_corpus_tables.sql
@@ -32,6 +32,7 @@
   status varchar(100) NOT NULL,
   created_by varchar(100) NOT NULL,
   deleted_by varchar(100) DEFAULT NULL,
+  status_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   UNIQUE INDEX unique_index (user_id,group_id),
   INDEX status_index(status),
   FOREIGN KEY (group_id) 
diff --git a/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql b/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
index 8476535..d1bbcb7 100644
--- a/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
+++ b/full/src/main/resources/db/new-sqlite/V1.1__create_virtual_corpus_tables.sql
@@ -35,6 +35,8 @@
   status varchar(100) NOT NULL,
   created_by varchar(100) NOT NULL,
   deleted_by varchar(100) DEFAULT NULL,
+-- interprets now as localtime and save it as UTC
+  status_date timestamp DEFAULT (datetime('now','localtime')),
   FOREIGN KEY (group_id) 
   	REFERENCES user_group (id)
   	ON DELETE CASCADE
diff --git a/full/src/main/resources/db/new-sqlite/V1.2__triggers.sql b/full/src/main/resources/db/new-sqlite/V1.2__triggers.sql
new file mode 100644
index 0000000..0acd2f7
--- /dev/null
+++ b/full/src/main/resources/db/new-sqlite/V1.2__triggers.sql
@@ -0,0 +1,9 @@
+--CREATE TRIGGER insert_member_status AFTER INSERT ON user_group_member
+--     BEGIN
+--      UPDATE user_group_member SET status_date = DATETIME('NOW', 'utc')  WHERE rowid = new.rowid;
+--     END;
+--
+CREATE TRIGGER update_member_status AFTER UPDATE ON user_group_member	
+     BEGIN
+      UPDATE user_group_member SET status_date = (datetime('now','localtime'))  WHERE rowid = old.rowid;
+     END;   
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index 625460f..5d3f9e9 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -41,9 +41,12 @@
 		<property name="ignoreResourceNotFound" value="true" />
 		<property name="locations">
 			<array>
-				<value>classpath:jdbc.properties</value>
+				<value>classpath:properties/jdbc.properties</value>
 				<value>file:./jdbc.properties</value>
-				<value>classpath:hibernate.properties</value>
+				<value>classpath:properties/mail.properties</value>
+				<value>file:./mail.properties</value>
+				<value>classpath:properties/hibernate.properties</value>
+				
 				<value>classpath:kustvakt.conf</value>
 				<value>file:./kustvakt.conf</value>
 			</array>
@@ -87,6 +90,12 @@
 		<property name="url" value="${jdbc.url}" />
 		<property name="username" value="${jdbc.username}" />
 		<property name="password" value="${jdbc.password}" />
+		<property name="connectionProperties">
+            <props>
+                <prop key="date_string_format">yyyy-MM-dd HH:mm:ss</prop>
+            </props>
+        </property>
+        
 		<!-- relevant for single connection datasource and sqlite -->
 		<property name="suppressClose">
 			<value>true</value>
@@ -306,4 +315,35 @@
 		<property name="dataSource" ref="sqliteDataSource" />
 	</bean>
 	
+	<!-- mail -->
+		<bean id="authenticator" class="de.ids_mannheim.korap.service.MailAuthenticator">
+		<constructor-arg index="0" value="${mail.username}"/>
+		<constructor-arg index="1" value="${mail.password}"/>
+	</bean>
+	<bean id="smtpSession" class="javax.mail.Session" factory-method="getInstance">
+		<constructor-arg index="0">
+			<props>
+				<prop key="mail.smtp.submitter">${mail.username}</prop>
+				<prop key="mail.smtp.auth">${mail.auth}</prop>
+				<prop key="mail.smtp.host">${mail.host}</prop>
+				<prop key="mail.smtp.port">${mail.port}</prop>
+				<prop key="mail.smtp.starttls.enable">${mail.starttls.enable}</prop>
+				<prop key="mail.smtp.connectiontimeout">${mail.connectiontimeout}</prop>
+			</props>
+		</constructor-arg>
+		<constructor-arg index="1" ref="authenticator"/>
+	</bean>
+	<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
+		<property name="username" value="${mail.username}" />
+	    <property name="password" value="${mail.password}" />
+		<property name="session" ref="smtpSession" />
+	</bean>
+	<bean id="velocityEngine" class="org.apache.velocity.app.VelocityEngine">
+		<constructor-arg index="0">
+			<props>
+				<prop key="resource.loader">class</prop>
+				<prop key="class.resource.loader.class">org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader</prop>
+			</props>
+		</constructor-arg>
+	</bean>
 </beans>
\ No newline at end of file
diff --git a/full/src/main/resources/index-kustvakt-example.zip b/full/src/main/resources/index-kustvakt-example.zip
deleted file mode 100644
index 33c4b5c..0000000
--- a/full/src/main/resources/index-kustvakt-example.zip
+++ /dev/null
Binary files differ
diff --git a/full/src/main/resources/kustvakt.conf b/full/src/main/resources/kustvakt.conf
index d8ff39d..d737a06 100644
--- a/full/src/main/resources/kustvakt.conf
+++ b/full/src/main/resources/kustvakt.conf
@@ -10,6 +10,18 @@
 ldap.config = file-path-to-ldap-config
 
 # Kustvakt
+## server
+server.port=8089
+server.host=localhost
+
+## mail settings
+mail.enabled = false
+mail.receiver = test@localhost
+mail.sender = noreply@ids-mannheim.de
+
+## mail.templates
+template.group.invitation = notification.vm
+
 ## default layers
 default.layer.p = tt
 default.layer.l = tt
diff --git a/full/src/main/resources/log4j.properties b/full/src/main/resources/log4j.properties
index 352062e..c747b68 100644
--- a/full/src/main/resources/log4j.properties
+++ b/full/src/main/resources/log4j.properties
@@ -4,9 +4,10 @@
 log4j.rootLogger=ERROR, stdout, debugLog
 log4j.logger.log=ERROR, errorLog
 
-#log4j.logger.de.ids_mannheim.korap.service.VirtualCorpusService = error, debugLog
 #log4j.logger.de.ids_mannheim.korap.web.controller.AuthenticationController = debug, debugLog, stdout
-#log4j.logger.de.ids_mannheim.korap.resource.rewrite.CollectionRewrite= stdout, debugLog
+#log4j.logger.de.ids_mannheim.korap.service.UserGroupService= debug, stdout, debugLog
+#log4j.logger.de.ids_mannheim.korap.rewrite.CollectionRewrite= debug, stdout, debugLog
+#log4j.logger.de.ids_mannheim.korap.service.MailService= debug, stdout, debugLog
 
 # Direct log messages to stdout
 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
diff --git a/full/src/main/resources/hibernate.properties b/full/src/main/resources/properties/hibernate.properties
similarity index 100%
rename from full/src/main/resources/hibernate.properties
rename to full/src/main/resources/properties/hibernate.properties
diff --git a/full/src/main/resources/jdbc.properties b/full/src/main/resources/properties/jdbc.properties
similarity index 96%
rename from full/src/main/resources/jdbc.properties
rename to full/src/main/resources/properties/jdbc.properties
index fb06a12..2bc85dd 100644
--- a/full/src/main/resources/jdbc.properties
+++ b/full/src/main/resources/properties/jdbc.properties
@@ -4,7 +4,7 @@
 
 #jdbc.database=mysql
 #jdbc.driverClassName=com.mysql.jdbc.Driver
-#jdbc.url=jdbc:mysql://localhost:3306/kustvakt?autoReconnect=true
+#jdbc.url=jdbc:mysql://localhost:3306/kustvakt?autoReconnect=true&useLegacyDatetimeCode=false
 #jdbc.username=korap
 #jdbc.password=password
 
diff --git a/full/src/main/resources/properties/mail.properties b/full/src/main/resources/properties/mail.properties
new file mode 100644
index 0000000..29d1ca1
--- /dev/null
+++ b/full/src/main/resources/properties/mail.properties
@@ -0,0 +1,7 @@
+mail.host = localhost
+mail.port = 25
+mail.connectiontimeout = 3000
+mail.auth = false
+mail.starttls.enable = false
+mail.username = username
+mail.password = password
\ No newline at end of file
diff --git a/full/src/main/resources/templates/notification.vm b/full/src/main/resources/templates/notification.vm
new file mode 100644
index 0000000..254ad83
--- /dev/null
+++ b/full/src/main/resources/templates/notification.vm
@@ -0,0 +1,12 @@
+<html>
+    <body>
+        <h3>Hi $username,</h3>
+        <p> you have been invited to group $group by $inviter! </p>
+        <p> Please login to <a href="https://korap.ids-mannheim.de/kalamar">KorAP</a> with your
+            account to accept or reject this invitation within 30 minutes. </p>
+        <p>After joining a group, you will be able to access virtual corpora shared with members of
+            the group. If you would like share your virtual corpus to a group, please contact the
+            group admin.</p>
+        <p> This is an automated generated email. Please do not reply directly to this e-mail. </p>
+    </body>
+</html>
\ No newline at end of file
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/AvailabilityTest.java
similarity index 83%
rename from full/src/test/java/de/ids_mannheim/korap/web/controller/SearchWithAvailabilityTest.java
rename to full/src/test/java/de/ids_mannheim/korap/web/controller/AvailabilityTest.java
index 02abd36..927e3f0 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/AvailabilityTest.java
@@ -1,13 +1,14 @@
 package de.ids_mannheim.korap.web.controller;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertNotNull;
 
-import org.eclipse.jetty.http.HttpHeaders;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.UniformInterfaceException;
@@ -18,10 +19,10 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
-public class SearchWithAvailabilityTest extends SpringJerseyTest {
+public class AvailabilityTest extends SpringJerseyTest {
     @Autowired
     private HttpAuthorizationHandler handler;
-    
+
     private void checkAndFree (String json) throws KustvaktException {
         JsonNode node = JsonUtils.readTree(json);
         assertEquals("availability",
@@ -38,6 +39,7 @@
     private void checkAndPublic (String json) throws KustvaktException {
         JsonNode node = JsonUtils.readTree(json);
         assertNotNull(node);
+
         assertEquals("operation:and",
                 node.at("/collection/operation").asText());
         assertEquals("match:eq",
@@ -49,21 +51,24 @@
         assertEquals("CC-BY.*",
                 node.at("/collection/operands/0/operands/0/value").asText());
         assertEquals("match:eq",
-                node.at("/collection/operands/0/operands/1/operands/0/match").asText());
+                node.at("/collection/operands/0/operands/1/operands/0/match")
+                        .asText());
         assertEquals("ACA.*",
-                node.at("/collection/operands/0/operands/1/operands/0/value").asText());
+                node.at("/collection/operands/0/operands/1/operands/0/value")
+                        .asText());
         assertEquals("match:eq",
-                node.at("/collection/operands/0/operands/1/operands/1/match").asText());
+                node.at("/collection/operands/0/operands/1/operands/1/match")
+                        .asText());
         assertEquals("QAO-NC",
-                node.at("/collection/operands/0/operands/1/operands/1/value").asText());
+                node.at("/collection/operands/0/operands/1/operands/1/value")
+                        .asText());
         assertEquals("operation:insertion",
                 node.at("/collection/rewrites/0/operation").asText());
         assertEquals("availability(PUB)",
                 node.at("/collection/rewrites/0/scope").asText());
     }
 
-    private void checkAndPublicWithACA (String json)
-            throws KustvaktException {
+    private void checkAndPublicWithACA (String json) throws KustvaktException {
         JsonNode node = JsonUtils.readTree(json);
         assertNotNull(node);
         assertEquals("operation:and",
@@ -72,7 +77,7 @@
                 node.at("/collection/rewrites/0/operation").asText());
         assertEquals("availability(PUB)",
                 node.at("/collection/rewrites/0/scope").asText());
-        
+
         assertEquals("match:eq",
                 node.at("/collection/operands/1/match").asText());
         assertEquals("type:regex",
@@ -80,16 +85,12 @@
         assertEquals("availability",
                 node.at("/collection/operands/1/key").asText());
         assertEquals("ACA.*", node.at("/collection/operands/1/value").asText());
-        
+
         node = node.at("/collection/operands/0");
-        assertEquals("match:eq",
-                node.at("/operands/0/match").asText());
-        assertEquals("type:regex",
-                node.at("/operands/0/type").asText());
-        assertEquals("availability",
-                node.at("/operands/0/key").asText());
-        assertEquals("CC-BY.*",
-                node.at("/operands/0/value").asText());
+        assertEquals("match:eq", node.at("/operands/0/match").asText());
+        assertEquals("type:regex", node.at("/operands/0/type").asText());
+        assertEquals("availability", node.at("/operands/0/key").asText());
+        assertEquals("CC-BY.*", node.at("/operands/0/value").asText());
 
         assertEquals("match:eq",
                 node.at("/operands/1/operands/0/match").asText());
@@ -99,7 +100,7 @@
                 node.at("/operands/1/operands/0/key").asText());
         assertEquals("ACA.*", node.at("/operands/1/operands/0/value").asText());
 
-        
+
     }
 
     private void checkAndAll (String json) throws KustvaktException {
@@ -140,7 +141,7 @@
                 node.at("/collection/rewrites/0/operation").asText());
         assertEquals("availability(ALL)",
                 node.at("/collection/rewrites/0/scope").asText());
-        
+
         assertEquals("match:eq",
                 node.at("/collection/operands/1/match").asText());
         assertEquals("type:regex",
@@ -148,17 +149,13 @@
         assertEquals("availability",
                 node.at("/collection/operands/1/key").asText());
         assertEquals("ACA.*", node.at("/collection/operands/1/value").asText());
-        
+
         node = node.at("/collection/operands/0");
-        
-        assertEquals("match:eq",
-                node.at("/operands/0/match").asText());
-        assertEquals("type:regex",
-                node.at("/operands/0/type").asText());
-        assertEquals("availability",
-                node.at("/operands/0/key").asText());
-        assertEquals("CC-BY.*",
-                node.at("/operands/0/value").asText());
+
+        assertEquals("match:eq", node.at("/operands/0/match").asText());
+        assertEquals("type:regex", node.at("/operands/0/type").asText());
+        assertEquals("availability", node.at("/operands/0/key").asText());
+        assertEquals("CC-BY.*", node.at("/operands/0/value").asText());
         assertEquals("match:eq",
                 node.at("/operands/1/operands/1/operands/0/match").asText());
         assertEquals("QAO-NC",
@@ -167,7 +164,7 @@
                 node.at("/operands/1/operands/1/operands/1/match").asText());
         assertEquals("QAO.*",
                 node.at("/operands/1/operands/1/operands/1/value").asText());
-        
+
     }
 
 
@@ -180,11 +177,13 @@
 
 
     private ClientResponse builtClientResponseWithIP (String collectionQuery,
-            String ip) throws UniformInterfaceException, ClientHandlerException, KustvaktException {
+            String ip) throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
         return resource().path("search").queryParam("q", "[orth=das]")
                 .queryParam("ql", "poliqarp").queryParam("cq", collectionQuery)
                 .header(Attributes.AUTHORIZATION,
-                        handler.createBasicAuthorizationHeaderValue("kustvakt", "kustvakt2015"))
+                        handler.createBasicAuthorizationHeaderValue("kustvakt",
+                                "kustvakt2015"))
                 .header(HttpHeaders.X_FORWARDED_FOR, ip)
                 .get(ClientResponse.class);
     }
@@ -244,7 +243,7 @@
                 response.getStatus());
 
         String json = response.getEntity(String.class);
-        
+
         JsonNode node = JsonUtils.readTree(json);
         assertEquals("operation:and",
                 node.at("/collection/operation").asText());
@@ -440,4 +439,61 @@
         checkAndAllWithACA(response.getEntity(String.class));
     }
 
+    @Test
+    public void testAvailabilityOr () throws KustvaktException {
+        ClientResponse response = builtSimpleClientResponse(
+                "availability=/CC-BY.*/ | availability=/ACA.*/");
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+
+        checkAndFree(response.getEntity(String.class));
+    }
+
+    @Test
+    public void testRedundancyOrPub () throws KustvaktException {
+        ClientResponse response = builtClientResponseWithIP(
+                "availability=/CC-BY.*/ | availability=/ACA.*/ | availability=/QAO-NC/",
+                "149.27.0.32");
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+
+        String json = response.getEntity(String.class);
+        JsonNode node = JsonUtils.readTree(json);
+        assertTrue(node.at("/collection/rewrites").isMissingNode());
+        assertEquals("operation:or", node.at("/collection/operation").asText());
+    }
+
+    @Test
+    public void testAvailabilityOrCorpusSigle () throws KustvaktException {
+        ClientResponse response = builtSimpleClientResponse(
+                "availability=/CC-BY.*/ | corpusSigle=GOE");
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+
+        checkAndFree(response.getEntity(String.class));
+    }
+
+    @Test
+    public void testOrWithoutAvailability () throws KustvaktException {
+        ClientResponse response = builtSimpleClientResponse(
+                "corpusSigle=GOE | textClass=politik");
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+
+        checkAndFree(response.getEntity(String.class));
+    }
+
+    @Test
+    public void testWithoutAvailability () throws KustvaktException {
+        ClientResponse response = builtSimpleClientResponse("corpusSigle=GOE");
+
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
+
+        checkAndFree(response.getEntity(String.class));
+    }
 }
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/MatchInfoControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/MatchInfoControllerTest.java
index 11a3193..4b0004a 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/MatchInfoControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/MatchInfoControllerTest.java
@@ -4,16 +4,15 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import org.eclipse.jetty.http.HttpHeaders;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientResponse;
 
 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.exceptions.KustvaktException;
 import de.ids_mannheim.korap.utils.JsonUtils;
 import de.ids_mannheim.korap.web.FastJerseyTest;
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchControllerTest.java
index 8d36bd8..b27e1bf 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/SearchControllerTest.java
@@ -10,17 +10,16 @@
 
 import javax.ws.rs.core.MediaType;
 
-import org.eclipse.jetty.http.HttpHeaders;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientResponse;
 
 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.ContextHolder;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.interfaces.db.EntityHandlerIface;
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 537f299..c907338 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
@@ -5,11 +5,11 @@
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 
-import org.eclipse.jetty.http.HttpHeaders;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.ClientResponse.Status;
@@ -236,6 +236,7 @@
                 node.at("/errors/0/1").asText());
     }
 
+    // EM: same as cancel invitation
     private void testDeletePendingMember () throws UniformInterfaceException,
             ClientHandlerException, KustvaktException {
         // dory delete pearl
@@ -270,14 +271,14 @@
                 .delete(ClientResponse.class);
 
         String entity = response.getEntity(String.class);
-        System.out.println(entity);
+        //        System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.DB_ENTRY_DELETED,
+        assertEquals(StatusCodes.GROUP_MEMBER_DELETED,
                 node.at("/errors/0/0").asInt());
-        assertEquals("pearl has already been deleted from the group.",
+        assertEquals("pearl has already been deleted from the group dory group",
                 node.at("/errors/0/1").asText());
-        assertEquals("pearl", node.at("/errors/0/2").asText());
+        assertEquals("[pearl, dory group]", node.at("/errors/0/2").asText());
     }
 
     private void testDeleteGroup (String groupId)
@@ -442,10 +443,12 @@
         //        System.out.println(entity);
         JsonNode node = JsonUtils.readTree(entity);
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.DB_ENTRY_EXISTS,
+        assertEquals(StatusCodes.GROUP_MEMBER_EXISTS,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Username marlin with status PENDING exists in user-group "
-                + "dory group", node.at("/errors/0/1").asText());
+        assertEquals(
+                "Username marlin with status PENDING exists in the user-group "
+                        + "dory group",
+                node.at("/errors/0/1").asText());
         assertEquals("[marlin, PENDING, dory group]",
                 node.at("/errors/0/2").asText());
     }
@@ -469,8 +472,6 @@
                                 "pass"))
                 .entity(userGroup).post(ClientResponse.class);
 
-//        String entity = response.getEntity(String.class);
-//        System.out.println(entity);
         assertEquals(Status.OK.getStatusCode(), response.getStatus());
 
         // check member
@@ -538,9 +539,9 @@
         JsonNode node = JsonUtils.readTree(entity);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.NOTHING_CHANGED,
+        assertEquals(StatusCodes.GROUP_MEMBER_DELETED,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Username pearl had been deleted in group 2",
+        assertEquals("pearl has already been deleted from the group dory group",
                 node.at("/errors/0/1").asText());
     }
 
@@ -578,9 +579,9 @@
         JsonNode node = JsonUtils.readTree(entity);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.NO_RESULT_FOUND,
+        assertEquals(StatusCodes.GROUP_MEMBER_NOT_FOUND,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Username bruce is not found in group 2",
+        assertEquals("bruce is not found in the group",
                 node.at("/errors/0/1").asText());
     }
 
@@ -601,9 +602,9 @@
         JsonNode node = JsonUtils.readTree(entity);
 
         assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
-        assertEquals(StatusCodes.NO_RESULT_FOUND,
+        assertEquals(StatusCodes.GROUP_NOT_FOUND,
                 node.at("/errors/0/0").asInt());
-        assertEquals("Username pearl is not found in group 100",
+        assertEquals("Group with id 100 is not found",
                 node.at("/errors/0/1").asText());
     }
 
@@ -625,4 +626,62 @@
         assertEquals(1, node.size());
     }
 
+    @Test
+    public void testUnsubscribeDeletedMember ()
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+        // pearl unsubscribes from dory group 
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        // dory group
+        form.add("groupId", "2");
+
+        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("pearl",
+                                "pass"))
+                .entity(form).post(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.GROUP_MEMBER_DELETED,
+                node.at("/errors/0/0").asInt());
+        assertEquals("pearl has already been deleted from the group dory group",
+                node.at("/errors/0/1").asText());
+        assertEquals("[pearl, dory group]", node.at("/errors/0/2").asText());
+    }
+
+    @Test
+    public void testUnsubscribePendingMember ()
+            throws UniformInterfaceException, ClientHandlerException,
+            KustvaktException {
+
+        JsonNode node = retrieveUserGroups("marlin");
+        assertEquals(2, node.size());
+
+        MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+        // dory group
+        form.add("groupId", "2");
+
+        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);
+
+        String entity = response.getEntity(String.class);
+        //        System.out.println(entity);
+        assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+        node = retrieveUserGroups("marlin");
+        assertEquals(1, node.size());
+
+        // invite marlin to dory group to set back the GroupMemberStatus.PENDING
+        testInviteDeletedMember();
+    }
 }
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 0df3720..e36fc0f 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
@@ -15,11 +15,11 @@
 import javax.ws.rs.core.MultivaluedMap;
 
 import org.apache.http.entity.ContentType;
-import org.eclipse.jetty.http.HttpHeaders;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.net.HttpHeaders;
 import com.sun.jersey.api.client.ClientHandlerException;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.ClientResponse.Status;
diff --git a/full/src/test/resources/kustvakt-test.conf b/full/src/test/resources/kustvakt-test.conf
index 3d5eadd..ca6b233 100644
--- a/full/src/test/resources/kustvakt-test.conf
+++ b/full/src/test/resources/kustvakt-test.conf
@@ -10,11 +10,18 @@
 ldap.config = file-path-to-ldap-config
 
 # Kustvakt
-
 ## server
 server.port=8089
 server.host=localhost
 
+## mail settings
+mail.enabled = false
+mail.receiver = test@localhost
+mail.sender = noreply@ids-mannheim.de
+
+## mail.templates
+template.group.invitation = notification.vm
+
 ## default layers
 default.layer.p = tt
 default.layer.l = tt
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index b416f1c..22da89a 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -36,6 +36,8 @@
 			<array>
 				<value>classpath:test-jdbc.properties</value>
 				<value>file:./test-jdbc.properties</value>
+				<value>classpath:properties/mail.properties</value>
+				<value>file:./mail.properties</value>
 				<value>classpath:test-hibernate.properties</value>
 				<value>classpath:kustvakt-test.conf</value>
 			</array>
@@ -75,22 +77,28 @@
 		<property name="url" value="${jdbc.url}" />
 		<property name="username" value="${jdbc.username}" />
 		<property name="password" value="${jdbc.password}" />
+		<property name="connectionProperties">
+			<props>
+				<prop key="date_string_format">yyyy-MM-dd HH:mm:ss</prop>
+			</props>
+		</property>
+
 		<!-- Sqlite can only have a single connection -->
 		<property name="suppressClose">
 			<value>true</value>
 		</property>
 	</bean>
-	
+
 	<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
-	    destroy-method="close">
-	    <property name="driverClass" value="${jdbc.driverClassName}" />
+		destroy-method="close">
+		<property name="driverClass" value="${jdbc.driverClassName}" />
 		<property name="jdbcUrl" value="${jdbc.url}" />
 		<property name="user" value="${jdbc.username}" />
 		<property name="password" value="${jdbc.password}" />
-	    <property name="maxPoolSize" value="4" />
-	    <property name="minPoolSize" value="1" />
-	    <property name="maxStatements" value="1" />
-	    <property name="testConnectionOnCheckout" value="true" />
+		<property name="maxPoolSize" value="4" />
+		<property name="minPoolSize" value="1" />
+		<property name="maxStatements" value="1" />
+		<property name="testConnectionOnCheckout" value="true" />
 	</bean>
 
 	<!-- to configure database for sqlite, mysql, etc. migrations -->
@@ -149,12 +157,12 @@
 	</bean>
 
 	<!-- Data access objects -->
-	<bean id="resourceDao" class="de.ids_mannheim.korap.dao.ResourceDao"/>
+	<bean id="resourceDao" class="de.ids_mannheim.korap.dao.ResourceDao" />
 	<!-- <bean id="annotationDao" class="de.ids_mannheim.korap.dao.AnnotationDao"/> -->
 
 	<!-- Krill -->
 	<bean id="search_krill" class="de.ids_mannheim.korap.web.SearchKrill">
-		<constructor-arg value="${krill.indexDir}"/>
+		<constructor-arg value="${krill.indexDir}" />
 	</bean>
 
 
@@ -165,11 +173,10 @@
 	<bean id="kustvakt_auditing" class="de.ids_mannheim.korap.handlers.JDBCAuditing">
 		<constructor-arg ref="kustvakt_db" />
 	</bean>
-	
-	<bean id="kustvakt_response"
-          class="de.ids_mannheim.korap.web.FullResponseHandler">
-          <constructor-arg index="0" name="iface" ref="kustvakt_auditing"/>
-    </bean>
+
+	<bean id="kustvakt_response" class="de.ids_mannheim.korap.web.FullResponseHandler">
+		<constructor-arg index="0" name="iface" ref="kustvakt_auditing" />
+	</bean>
 
 	<bean id="kustvakt_userdb" class="de.ids_mannheim.korap.handlers.EntityDao">
 		<constructor-arg ref="kustvakt_db" />
@@ -197,15 +204,14 @@
 	</bean>
 
 	<!-- authentication providers to use -->
-	<!-- <bean id="api_auth" class="de.ids_mannheim.korap.authentication.APIAuthentication">
-		<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration"
-			ref="kustvakt_config" />
-	</bean> -->
+	<!-- <bean id="api_auth" class="de.ids_mannheim.korap.authentication.APIAuthentication"> 
+		<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration" 
+		ref="kustvakt_config" /> </bean> -->
 	<bean id="ldap_auth" class="de.ids_mannheim.korap.authentication.LdapAuth3">
 		<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration"
 			ref="kustvakt_config" />
 	</bean>
-	
+
 	<bean id="openid_auth"
 		class="de.ids_mannheim.korap.authentication.OpenIDconnectAuthentication">
 		<constructor-arg type="de.ids_mannheim.korap.config.KustvaktConfiguration"
@@ -214,7 +220,8 @@
 			type="de.ids_mannheim.korap.interfaces.db.PersistenceClient" ref="kustvakt_db" />
 	</bean>
 
-	<bean id="basic_auth" class="de.ids_mannheim.korap.authentication.BasicAuthentication" />
+	<bean id="basic_auth"
+		class="de.ids_mannheim.korap.authentication.BasicAuthentication" />
 
 
 	<bean id="session_auth"
@@ -299,4 +306,35 @@
 		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 		<property name="dataSource" ref="dataSource" />
 	</bean>
+
+	<!-- mail -->
+	<bean id="authenticator" class="de.ids_mannheim.korap.service.MailAuthenticator">
+		<constructor-arg index="0" value="${mail.username}"/>
+		<constructor-arg index="1" value="${mail.password}"/>
+	</bean>
+	<bean id="smtpSession" class="javax.mail.Session" factory-method="getInstance">
+		<constructor-arg index="0">
+			<props>
+				<prop key="mail.smtp.submitter">${mail.username}</prop>
+				<prop key="mail.smtp.auth">${mail.auth}</prop>
+				<prop key="mail.smtp.host">${mail.host}</prop>
+				<prop key="mail.smtp.port">${mail.port}</prop>
+				<prop key="mail.smtp.starttls.enable">${mail.starttls.enable}</prop>
+				<prop key="mail.smtp.connectiontimeout">${mail.connectiontimeout}</prop>
+			</props>
+		</constructor-arg>
+		<constructor-arg index="1" ref="authenticator"/>
+	</bean>
+	<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
+		<property name="session" ref="smtpSession" />
+	</bean>
+	<bean id="velocityEngine" class="org.apache.velocity.app.VelocityEngine">
+		<constructor-arg index="0">
+			<props>
+				<prop key="resource.loader">class</prop>
+				<prop key="class.resource.loader.class">org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+				</prop>
+			</props>
+		</constructor-arg>
+	</bean>
 </beans>
\ No newline at end of file
diff --git a/lite/Changes b/lite/Changes
index 4e615e9..6355c0f 100644
--- a/lite/Changes
+++ b/lite/Changes
@@ -1,8 +1,11 @@
-0.59.9 2018-02-01
+version 0.59.9 
+01/02/2018
 	- renamed light to lite (margaretha)
 	- added Changes file (margaretha)
 	- updated library versions and java environment (margaretha)
-0.59.8 2018-01-17 
+	
+version 0.59.8 
+17/01/2018 
 	- restructured Kustvakt and created /lite project
 	- removed version from service paths (margaretha)
 	- updated query serialization tests (margaretha)
diff --git a/lite/src/main/java/de/ids_mannheim/korap/server/KustvaktLiteServer.java b/lite/src/main/java/de/ids_mannheim/korap/server/KustvaktLiteServer.java
index c0e31f3..acac717 100644
--- a/lite/src/main/java/de/ids_mannheim/korap/server/KustvaktLiteServer.java
+++ b/lite/src/main/java/de/ids_mannheim/korap/server/KustvaktLiteServer.java
@@ -22,10 +22,4 @@
         server.start();
     }
 
-    @Override
-    protected void setup () {
-        // TODO Auto-generated method stub
-
-    }
-
 }