Testing mail with jetty jndi.

Change-Id: If4393b189e1eaa4380cbccfc8e126e336d122945
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..564832c 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);
 	}
@@ -215,9 +217,9 @@
 	     	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 {
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..ad6aaab 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,6 +14,13 @@
  */
 
 public class FullConfiguration extends KustvaktConfiguration {
+    // mail configuration
+    private boolean isMailEnabled;
+    private String testEmail;
+    private String mailUsername;
+    private String mailPassword;
+    private String mailSmtp;
+    private String mailPort;
 
     private String ldapConfig;
 
@@ -49,12 +56,28 @@
         // 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")));
+        setTestEmail(properties.getProperty("mail.receiver"));
+        if (isMailEnabled){
+            // other properties must be set in the kustvakt.conf
+            setMailUsername(properties.getProperty("mail.username"));
+            setMailPassword(properties.getProperty("mail.password"));
+            setMailSmtp(properties.getProperty("mail.smtp"));
+            setMailPort(properties.getProperty("mail.port"));
+        }
     }
 
     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 +240,52 @@
         this.isSoftDeleteAutoGroup = isSoftDeleteAutoGroup;
     }
 
+    public String getTestEmail () {
+        return testEmail;
+    }
+
+    public void setTestEmail (String testEmail) {
+        this.testEmail = testEmail;
+    }
+
+    public String getMailUsername () {
+        return mailUsername;
+    }
+
+    public void setMailUsername (String mailUsername) {
+        this.mailUsername = mailUsername;
+    }
+
+    public String getMailPassword () {
+        return mailPassword;
+    }
+
+    public void setMailPassword (String mailPassword) {
+        this.mailPassword = mailPassword;
+    }
+
+    public String getMailSmtp () {
+        return mailSmtp;
+    }
+
+    public void setMailSmtp (String mailHost) {
+        this.mailSmtp = mailHost;
+    }
+
+    public String getMailPort () {
+        return mailPort;
+    }
+
+    public void setMailPort (String mailPort) {
+        this.mailPort = mailPort;
+    }
+
+    public boolean isMailEnabled () {
+        return isMailEnabled;
+    }
+
+    public void setMailEnabled (boolean isMailEnabled) {
+        this.isMailEnabled = isMailEnabled;
+    }
+
 }
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..6871205 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,22 @@
 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 javax.naming.NamingException;
+
+import org.eclipse.jetty.jndi.factories.MailSessionReference;
+import org.eclipse.jetty.plus.jndi.Resource;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.webapp.Configuration.ClassList;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
 import de.ids_mannheim.korap.web.KustvaktBaseServer;
 
 /**
@@ -11,17 +27,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.getClassLoader().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" });
@@ -31,52 +65,30 @@
     }
 
     @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);
-//                }
-//            }
-//        }
+    protected void setupJndi(Server server, WebAppContext webapp) {
+        
+        
+//        ClassList classlist = ClassList.setServerDefault(server);
+//        classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
+//                "org.eclipse.jetty.plus.webapp.EnvConfiguration",
+//                "org.eclipse.jetty.plus.webapp.PlusConfiguration");
+        
+        MailSessionReference mailref = new MailSessionReference();
+        mailref.setUser(fullConfig.getMailUsername());
+        mailref.setPassword(fullConfig.getMailPassword());
+        
+        Properties props = new Properties();
+        props.put("mail.smtp.auth", "false");
+        props.put("mail.smtp.host",fullConfig.getMailSmtp());
+        props.put("mail.from",fullConfig.getMailUsername());
+        props.put("mail.debug", "false");
+        mailref.setProperties(props);
+        try {
+            new Resource(webapp, "mail/Session", mailref);
+        }
+        catch (NamingException e) {
+            e.printStackTrace();
+        }
     }
+    
 }
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..74164d5
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/service/MailService.java
@@ -0,0 +1,63 @@
+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.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.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.user.User;
+
+@Service
+public class MailService {
+    @Autowired
+    private AuthenticationManagerIface authManager;
+//    @Autowired
+    private JavaMailSender mailSender;
+//    @Autowired
+    private VelocityEngine velocityEngine;
+
+    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("noreply-korap-notification@ids-mannheim.de");
+                message.setSubject("Invitation to join group");
+                message.setText(prepareText(inviteeName, groupName, inviter),
+                        true);
+            }
+
+        };
+        mailSender.send(preparator);
+    }
+
+    private String prepareText (String username, String groupName,
+            String inviter) {
+        Context context = new VelocityContext();
+        context.put("username", username);
+        context.put("groupName", groupName);
+        context.put("inviter", inviter);
+
+        StringWriter stringWriter = new StringWriter();
+        velocityEngine.mergeTemplate("invitationNotification.vm",
+                StandardCharsets.UTF_16.name(), context, stringWriter);
+        return stringWriter.toString();
+    }
+}
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 1dfd16f..e05053e 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,11 +1,8 @@
 package de.ids_mannheim.korap.service;
 
-import java.time.LocalDateTime;
-import java.time.ZoneId;
 import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Date;
 import java.util.List;
 
 import org.slf4j.Logger;
@@ -57,6 +54,8 @@
     private AuthenticationManagerIface authManager;
     @Autowired
     private FullConfiguration config;
+    @Autowired
+    private MailService mailService;
 
     private static List<Role> memberRoles;
 
@@ -176,7 +175,7 @@
                 // skip owner, already added while creating group.
                 continue;
             }
-            addGroupMember(memberId, userGroup, createdBy,
+            inviteGroupMember(memberId, userGroup, createdBy,
                     GroupMemberStatus.PENDING);
         }
     }
@@ -226,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 {
 
@@ -249,8 +248,12 @@
         member.setRoles(memberRoles);
         member.setStatus(status);
         member.setUserId(username);
-
         groupMemberDao.addMember(member);
+
+        if (config.isMailEnabled()) {
+            mailService.sendMemberInvitationNotification(username,
+                    userGroup.getName(), createdBy);
+        }
     }
 
     private boolean memberExists (String username, int groupId,
@@ -277,7 +280,7 @@
         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();
@@ -285,16 +288,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);
         }
     }
 
@@ -322,9 +325,9 @@
 
         ParameterChecker.checkStringValue(username, "userId");
         ParameterChecker.checkIntegerValue(groupId, "groupId");
-        
+
         UserGroup group = userGroupDao.retrieveGroupById(groupId);
-        
+
         UserGroupMember member =
                 groupMemberDao.retrieveMemberById(username, groupId);
         GroupMemberStatus status = member.getStatus();
@@ -337,22 +340,21 @@
         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(),
+                            + " exists in the user-group " + group.getName(),
                     username, status.name(), group.getName());
         }
         // status pending
         else {
-            jlog.debug("status: " +member.getStatusDate());
+            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)){
+            if (expiration.isAfter(now)) {
                 member.setStatus(GroupMemberStatus.ACTIVE);
                 groupMemberDao.updateMember(member);
             }
-            else{
+            else {
                 throw new KustvaktException(StatusCodes.INVITATION_EXPIRED);
             }
         }
@@ -405,9 +407,9 @@
      */
     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();
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 eda4eeb..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
@@ -166,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) {
diff --git a/full/src/main/resources/WEB-INF/web.xml b/full/src/main/resources/WEB-INF/web.xml
new file mode 100644
index 0000000..4c2a254
--- /dev/null
+++ b/full/src/main/resources/WEB-INF/web.xml
@@ -0,0 +1,5 @@
+<resource-ref>
+  <res-ref-name>mail/Session</res-ref-name>
+  <res-type>javax.mail.Session</res-type>
+  <res-auth>Container</res-auth>
+</resource-ref>
\ No newline at end of file
diff --git a/full/src/main/resources/kustvakt.conf b/full/src/main/resources/kustvakt.conf
index d8ff39d..2e29c02 100644
--- a/full/src/main/resources/kustvakt.conf
+++ b/full/src/main/resources/kustvakt.conf
@@ -10,6 +10,9 @@
 ldap.config = file-path-to-ldap-config
 
 # Kustvakt
+## mail settings
+mail.enabled = false
+
 ## default layers
 default.layer.p = tt
 default.layer.l = tt
diff --git a/full/src/main/resources/templates/invitationNotification.vm b/full/src/main/resources/templates/invitationNotification.vm
new file mode 100644
index 0000000..02cf5c6
--- /dev/null
+++ b/full/src/main/resources/templates/invitationNotification.vm
@@ -0,0 +1,13 @@
+<html>
+	<body>
+		<h3>Hi ${username}, you are invited to group {$group} by ${inviter}!</h3>
+		<p>
+			Login to KorAP to accept or reject the invitation.
+			<br /> 
+			The invitation is valid for 30 minutes.
+		</p>
+		<p>
+			Do not reply! This is an automated generated email.
+		</p>
+	</body>
+</html>
\ No newline at end of file