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 ef135be3d6312f92a377ad0418500f7cc71f06e0..1504a0494480af434e543f437c7633c9c7a78836 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 d7e3f6950c06ffa763b827718f073e5911069b0f..b864cac3ad13e1dcce943712d9a7bfa6a3c6c666 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 de50b5560a43609c494e956ae5763d7ee5c873bb..fcdcf4db53855c0e1b0383fdcf951aee67052580 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 ef4ada5205b28702305a50b1bad8129d0d1815b1..4dd9f94ad41443524aba635894f7b75509bd600c 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 cdd9eaa20539d50686eea5a6f9210f4b30072991..776ce8a92413d76cf1aad7b0e2f2135b4a20b501 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 a19af703d3474898c7f95b347d34d7a80a0bb733..2f242abb3d908bc74b79e8261060294d007cd46f 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 c01a7d174e59902a1b08b1f1874898263251ca56..454fde42dfde6e23bea7b103aeb7f3ae16ca19d6 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 7e0733a34de7ed0e145220ff1adb7477a64cf6df..a4832f3aa9371cf8b6f367ef26383d19612d9430 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 0b4847bb0e2fd2b403041c9bf406973773cd7bcd..93ecfed65f55e410543ce675469107aef7fa10b5 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 0a0ea3c46c910fdf65fe8e7540f177a04abc3e53..b80ee94bcb83158813605b2d5c76782e56899c55 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 9b7888d46d9d8df7ed778f8313948a15dcd5c485..3d677f09ee6e316d2ff977c6310e3ef5d12f4ca9 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;