From 6d6c792eb93f2f1d4801cae4a7c0146d6fd5800e Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 1 Mar 2017 14:15:58 +0100
Subject: [PATCH] OO-2535: send the mails asynchronous

---
 .../org/olat/core/util/mail/MailManager.java  |  9 +++++
 .../core/util/mail/_spring/mailContext.xml    | 10 ++++++
 .../util/mail/manager/MailManagerImpl.java    | 34 +++++++++++++++++++
 .../org/olat/course/_spring/courseContext.xml |  2 +-
 .../iq/QTI21AssessmentRunController.java      |  9 ++---
 .../org/olat/ims/qti21/OutcomesListener.java  |  3 +-
 .../ims/qti21/manager/QTI21ServiceImpl.java   |  7 +---
 .../qti21/model/InMemoryOutcomeListener.java  |  3 +-
 .../ui/AssessmentEntryOutcomesListener.java   | 11 +++---
 .../ui/AssessmentTestDisplayController.java   |  2 +-
 .../ui/QTI21AssessmentDetailsController.java  |  4 +--
 11 files changed, 74 insertions(+), 20 deletions(-)

diff --git a/src/main/java/org/olat/core/util/mail/MailManager.java b/src/main/java/org/olat/core/util/mail/MailManager.java
index ef135be3d63..1504a049448 100644
--- a/src/main/java/org/olat/core/util/mail/MailManager.java
+++ b/src/main/java/org/olat/core/util/mail/MailManager.java
@@ -180,6 +180,15 @@ public interface MailManager {
 	 */
 	public MailerResult sendMessage(MailBundle... bundles);
 	
+	/**
+	 * Send the mail bundle asynchronous. The queue is in memory (for the moment)
+	 * and a shut down of the queue will mean looses.
+	 * 
+	 * @param bundles
+	 * @return
+	 */
+	public void sendMessageAsync(MailBundle... bundles);
+	
 	public MailerResult sendExternMessage(MailBundle bundle, MailerResult result, boolean useTemplate);
 	
 	public MimeMessage createMimeMessage(Address from, Address[] tos, Address[] ccs, Address[] bccs, String subject, String body,
diff --git a/src/main/java/org/olat/core/util/mail/_spring/mailContext.xml b/src/main/java/org/olat/core/util/mail/_spring/mailContext.xml
index d7e3f6950c0..b864cac3ad1 100644
--- a/src/main/java/org/olat/core/util/mail/_spring/mailContext.xml
+++ b/src/main/java/org/olat/core/util/mail/_spring/mailContext.xml
@@ -13,6 +13,16 @@
 		<property name="className" value="org.olat.core.util.mail.ui.SendDocumentsByEMailController"/>
 	</bean>
 	
+		<bean id="mailAsyncExecutorService" class="org.springframework.core.task.support.ExecutorServiceAdapter">
+		<constructor-arg index="0" ref="mailAsyncExecutor" />
+	</bean>
+	
+	<bean id="mailAsyncExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
+		<property name="corePoolSize" value="1" />
+		<property name="maxPoolSize" value="1" />
+		<property name="queueCapacity" value="2000" />
+	</bean>
+	
 	<!-- Mail admin. panel -->
 	<bean class="org.olat.core.extensions.action.GenericActionExtension" id="sysadmin.menupoint.syscfg.mailcfg" init-method="initExtensionPoints">
 		<property name="order" value="7208" />
diff --git a/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java b/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java
index de50b5560a4..fcdcf4db538 100644
--- a/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java
+++ b/src/main/java/org/olat/core/util/mail/manager/MailManagerImpl.java
@@ -38,6 +38,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.zip.Adler32;
 
 import javax.activation.DataHandler;
@@ -71,12 +73,14 @@ import org.apache.velocity.exception.ResourceNotFoundException;
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.olat.basesecurity.IdentityImpl;
 import org.olat.basesecurity.IdentityRef;
+import org.olat.core.CoreSpringFactory;
 import org.olat.core.commons.persistence.DB;
 import org.olat.core.commons.services.notifications.NotificationsManager;
 import org.olat.core.commons.services.notifications.Publisher;
 import org.olat.core.commons.services.notifications.PublisherData;
 import org.olat.core.commons.services.notifications.Subscriber;
 import org.olat.core.commons.services.notifications.SubscriptionContext;
+import org.olat.core.commons.services.taskexecutor.model.DBSecureRunnable;
 import org.olat.core.helpers.Settings;
 import org.olat.core.id.Identity;
 import org.olat.core.id.OLATResourceable;
@@ -112,6 +116,7 @@ import org.olat.core.util.vfs.VFSLeaf;
 import org.olat.core.util.vfs.VFSManager;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.stereotype.Service;
 
 import com.sun.mail.smtp.SMTPMessage;
@@ -138,6 +143,8 @@ public class MailManagerImpl implements MailManager, InitializingBean  {
 
 	@Autowired
 	private DB dbInstance;
+	@Autowired @Qualifier("mailAsyncExecutorService")
+	private ExecutorService asyncExecutor;
 	@Autowired
 	private NotificationsManager notificationsManager;
 	private final MailModule mailModule;
@@ -673,6 +680,19 @@ public class MailManagerImpl implements MailManager, InitializingBean  {
 		return bundle;
 	}
 
+	@Override
+	public void sendMessageAsync(MailBundle... bundles) {
+		try {
+			SendMail sendMail = new SendMail(bundles);
+			DBSecureRunnable command = new DBSecureRunnable(sendMail);
+			asyncExecutor.execute(command);
+		} catch (RejectedExecutionException e) {
+			log.error("Queue full, email lost", e);
+		} catch (Exception e) {
+			log.error("", e);
+		}
+	}
+
 	@Override
 	public MailerResult sendMessage(MailBundle... bundles) {
 		MailerResult result = new MailerResult();
@@ -1710,6 +1730,20 @@ public class MailManagerImpl implements MailManager, InitializingBean  {
 		}
 	}
 	
+	public static class SendMail implements Runnable {
+		
+		private final MailBundle[] bundles;
+		
+		public SendMail(MailBundle[] bundles) {
+			this.bundles = bundles;
+		}
+
+		@Override
+		public void run() {
+			CoreSpringFactory.getImpl(MailManager.class).sendMessage(bundles);
+		}
+	}
+	
 	private static class VFSDataSource implements DataSource {
 		
 		private final String name;
diff --git a/src/main/java/org/olat/course/_spring/courseContext.xml b/src/main/java/org/olat/course/_spring/courseContext.xml
index ef4ada5205b..4dd9f94ad41 100644
--- a/src/main/java/org/olat/course/_spring/courseContext.xml
+++ b/src/main/java/org/olat/course/_spring/courseContext.xml
@@ -24,7 +24,7 @@
 	<import resource="classpath:/org/olat/course/statistic/_spring/statisticContext.xml"/>
 	<import resource="classpath:/org/olat/course/statistic/_spring/statisticsJobContext.xml"/>
 				
-<bean id="courseFactory" class="org.olat.course.CourseFactory"
+	<bean id="courseFactory" class="org.olat.course.CourseFactory"
 		depends-on="businessGroupService,resourceManager,baseSecurityManager,glossaryManager">
 		<constructor-arg index="0" ref="coordinatorManager" />
 		<constructor-arg index="1" ref="repositoryManager" />
diff --git a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
index cdd9eaa2053..776ce8a9241 100644
--- a/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
+++ b/src/main/java/org/olat/course/nodes/iq/QTI21AssessmentRunController.java
@@ -563,15 +563,16 @@ public class QTI21AssessmentRunController extends BasicController implements Gen
 	}
 	
 	@Override
-	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Locale locale) {
-		decorateCourseConfirmation(candidateSession, options, userCourseEnv.getCourseEnvironment(), courseNode, testEntry, locale);
+	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Date timestamp, Locale locale) {
+		decorateCourseConfirmation(candidateSession, options, userCourseEnv.getCourseEnvironment(), courseNode, testEntry, timestamp, locale);
 	}
 	
 	public static void decorateCourseConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options,
-			CourseEnvironment courseEnv, CourseNode courseNode, RepositoryEntry testEntry, Locale locale)  {
+			CourseEnvironment courseEnv, CourseNode courseNode, RepositoryEntry testEntry, Date timestamp, Locale locale)  {
 		MailBundle bundle = new MailBundle();
 		bundle.setToId(candidateSession.getIdentity());
 		String fullname = CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(candidateSession.getIdentity());
+		Date assessedDate = candidateSession.getFinishTime() == null ? timestamp : candidateSession.getFinishTime();
 
 		String[] args = new String[] {
 				courseEnv.getCourseTitle(),						// {0}
@@ -581,7 +582,7 @@ public class QTI21AssessmentRunController extends BasicController implements Gen
 				testEntry.getDisplayname(),						// {4}
 				fullname,										// {5}
 				Formatter.getInstance(locale)
-					.formatDateAndTime(candidateSession.getFinishTime()) // {6}
+					.formatDateAndTime(assessedDate) 			// {6}
 		};
 
 		Translator translator = Util.createPackageTranslator(QTI21AssessmentRunController.class, locale);
diff --git a/src/main/java/org/olat/ims/qti21/OutcomesListener.java b/src/main/java/org/olat/ims/qti21/OutcomesListener.java
index a19af703d34..2f242abb3d9 100644
--- a/src/main/java/org/olat/ims/qti21/OutcomesListener.java
+++ b/src/main/java/org/olat/ims/qti21/OutcomesListener.java
@@ -19,6 +19,7 @@
  */
 package org.olat.ims.qti21;
 
+import java.util.Date;
 import java.util.Locale;
 
 import org.olat.ims.qti21.model.DigitalSignatureOptions;
@@ -38,7 +39,7 @@ public interface OutcomesListener {
 	 * @param options
 	 * @param locale
 	 */
-	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Locale locale);
+	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Date timestamp, Locale locale);
 	
 	/**
 	 * Update the outcomes.
diff --git a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
index c01a7d174e5..454fde42dfd 100644
--- a/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
+++ b/src/main/java/org/olat/ims/qti21/manager/QTI21ServiceImpl.java
@@ -66,7 +66,6 @@ import org.olat.core.util.crypto.CryptoUtil;
 import org.olat.core.util.crypto.X509CertificatePrivateKeyPair;
 import org.olat.core.util.mail.MailBundle;
 import org.olat.core.util.mail.MailManager;
-import org.olat.core.util.mail.MailerResult;
 import org.olat.core.util.xml.XMLDigitalSignatureUtil;
 import org.olat.core.util.xml.XStreamHelper;
 import org.olat.fileresource.FileResourceManager;
@@ -656,12 +655,8 @@ public class QTI21ServiceImpl implements QTI21Service, UserDataDeletable, Initia
 				List<File> attachments = new ArrayList<>(2);
 				attachments.add(signatureFile);
 				mail.getContent().setAttachments(attachments);
-				MailerResult result = mailManager.sendMessage(mail);
-				if(result.getReturnCode() != MailerResult.OK) {
-					log.error("Confirmation mail cannot be send");
-				}
+				mailManager.sendMessageAsync(mail);
 			}
-			
 		} catch (Exception e) {
 			log.error("", e);
 		}
diff --git a/src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java b/src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java
index 7e0733a34de..a4832f3aa93 100644
--- a/src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java
+++ b/src/main/java/org/olat/ims/qti21/model/InMemoryOutcomeListener.java
@@ -19,6 +19,7 @@
  */
 package org.olat.ims.qti21.model;
 
+import java.util.Date;
 import java.util.Locale;
 
 import org.olat.ims.qti21.AssessmentTestSession;
@@ -33,7 +34,7 @@ import org.olat.ims.qti21.OutcomesListener;
 public class InMemoryOutcomeListener implements OutcomesListener {
 
 	@Override
-	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Locale locale) {
+	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Date timestamp, Locale locale) {
 		//do nothing
 	}
 
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentEntryOutcomesListener.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentEntryOutcomesListener.java
index 0b4847bb0e2..93ecfed65f5 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentEntryOutcomesListener.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentEntryOutcomesListener.java
@@ -20,6 +20,7 @@
 package org.olat.ims.qti21.ui;
 
 import java.math.BigDecimal;
+import java.util.Date;
 import java.util.Locale;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -65,14 +66,16 @@ public class AssessmentEntryOutcomesListener implements OutcomesListener {
 	}
 
 	@Override
-	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Locale locale) {
-		decorateResourceConfirmation(candidateSession, options, locale);
+	public void decorateConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Date timestamp, Locale locale) {
+		decorateResourceConfirmation(candidateSession, options, timestamp, locale);
 	}
 	
-	public static void decorateResourceConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Locale locale) {
+	public static void decorateResourceConfirmation(AssessmentTestSession candidateSession, DigitalSignatureOptions options, Date timestamp, Locale locale) {
 		MailBundle bundle = new MailBundle();
 		bundle.setToId(candidateSession.getIdentity());
 		String fullname = CoreSpringFactory.getImpl(UserManager.class).getUserDisplayName(candidateSession.getIdentity());
+		Date assessedDate = candidateSession.getFinishTime() == null ? timestamp : candidateSession.getFinishTime();
+
 		
 		Translator translator = Util.createPackageTranslator(QTI21RuntimeController.class, locale);
 		RepositoryEntry entry = candidateSession.getRepositoryEntry();
@@ -85,7 +88,7 @@ public class AssessmentEntryOutcomesListener implements OutcomesListener {
 				testEntry.getDisplayname(),	// {4}
 				fullname,					// {5}
 				Formatter.getInstance(locale)
-					.formatDateAndTime(candidateSession.getFinishTime()) // {6}
+					.formatDateAndTime(assessedDate) // {6}
 		};
 
 		String subject = translator.translate("digital.signature.mail.subject", args);
diff --git a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
index 0a0ea3c46c9..b80ee94bcb8 100644
--- a/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/AssessmentTestDisplayController.java
@@ -1143,7 +1143,7 @@ public class AssessmentTestDisplayController extends BasicController implements
 		boolean digitalSignature = deliveryOptions.isDigitalSignature() && qtiModule.isDigitalSignatureEnabled();
 		DigitalSignatureOptions options = new DigitalSignatureOptions(digitalSignature, sendMail, entry, testEntry);
 		if(digitalSignature) {
-			outcomesListener.decorateConfirmation(candidateSession, options, getLocale());
+			outcomesListener.decorateConfirmation(candidateSession, options, getCurrentRequestTimestamp(), getLocale());
 		}
 		return options;
 	}
diff --git a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
index 9b7888d46d9..3d677f09ee6 100644
--- a/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
+++ b/src/main/java/org/olat/ims/qti21/ui/QTI21AssessmentDetailsController.java
@@ -390,10 +390,10 @@ public class QTI21AssessmentDetailsController extends FormBasicController {
 		DigitalSignatureOptions options = new DigitalSignatureOptions(digitalSignature, sendMail, entry, testEntry);
 		if(digitalSignature) {
 			if(courseNode == null) {
-				 AssessmentEntryOutcomesListener.decorateResourceConfirmation(session, options, getLocale());
+				 AssessmentEntryOutcomesListener.decorateResourceConfirmation(session, options, null, getLocale());
 			} else {
 				CourseEnvironment courseEnv = CourseFactory.loadCourse(entry).getCourseEnvironment();
-				QTI21AssessmentRunController.decorateCourseConfirmation(session, options, courseEnv, courseNode, sessionTestEntry, getLocale());
+				QTI21AssessmentRunController.decorateCourseConfirmation(session, options, courseEnv, courseNode, sessionTestEntry, null, getLocale());
 			}
 		}
 		return options;
-- 
GitLab