use of com.helger.smtp.data.IMutableEmailData in project ph-web by phax.
the class MailAPI method queueMails.
/**
* Queue more than one mail.
*
* @param aSMTPSettings
* The SMTP settings to be used.
* @param aMailDataList
* The mail messages to queue. May not be <code>null</code>.
* @return The number of queued emails. Always ≥ 0. Maximum value is the
* number of {@link IMutableEmailData} objects in the argument.
*/
@Nonnegative
public static int queueMails(@Nonnull final ISMTPSettings aSMTPSettings, @Nonnull final Collection<? extends IMutableEmailData> aMailDataList) {
ValueEnforcer.notNull(aSMTPSettings, "SmtpSettings");
ValueEnforcer.notNull(aMailDataList, "MailDataList");
if (aMailDataList.isEmpty())
throw new IllegalArgumentException("At least one message has to be supplied!");
MailQueuePerSMTP aSMTPQueue;
RW_LOCK.writeLock().lock();
try {
// get queue per SMTP settings
try {
aSMTPQueue = _getOrCreateMailQueuePerSMTP(aSMTPSettings);
} catch (final IllegalStateException ex) {
// Happens if queue is already stopped
// Put all errors in failed mail queue
final MailTransportError aError = new MailTransportError(ex);
for (final IMutableEmailData aEmailData : aMailDataList) getFailedMailQueue().add(new FailedMailData(aSMTPSettings, aEmailData, aError));
return 0;
}
} finally {
RW_LOCK.writeLock().unlock();
}
int nQueuedMails = 0;
final boolean bSendVendorOnlyMails = GlobalDebug.isDebugMode();
// submit all messages
for (final IMutableEmailData aEmailData : aMailDataList) {
if (aEmailData == null) {
LOGGER.error("Mail data is null! Ignoring this item completely.");
continue;
}
// queue the mail
STATS_MAILS_QUEUED.increment();
// Do some consistency checks to ensure this particular email can be
// send
boolean bCanQueue = true;
if (aEmailData.getFrom() == null) {
if (LOGGER.isErrorEnabled())
LOGGER.error("Mail data has no sender address: " + aEmailData + " - not queuing!");
bCanQueue = false;
}
if (aEmailData.to().isEmpty()) {
if (LOGGER.isErrorEnabled())
LOGGER.error("Mail data has no receiver address: " + aEmailData + " - not queuing!");
bCanQueue = false;
}
if (bSendVendorOnlyMails) {
// In the debug version we can *only* send to vendor addresses!
if (hasNonVendorEmailAddress(aEmailData.to()) || hasNonVendorEmailAddress(aEmailData.cc()) || hasNonVendorEmailAddress(aEmailData.bcc())) {
if (LOGGER.isErrorEnabled())
LOGGER.error("Debug mode: ignoring mail TO '" + aEmailData.to() + "'" + (aEmailData.cc().isNotEmpty() ? " and CC '" + aEmailData.cc() + "'" : "") + (aEmailData.bcc().isNotEmpty() ? " and BCC '" + aEmailData.bcc() + "'" : "") + " because at least one address is not targeted to the vendor domain '" + VendorInfo.getVendorEmailSuffix() + "'");
bCanQueue = false;
}
}
// Check if queue is already stopped
if (aSMTPQueue.isStopped()) {
LOGGER.error("Queue is already stopped - not queuing!");
bCanQueue = false;
}
boolean bWasQueued = false;
Exception aException = null;
if (bCanQueue) {
// Check if a subject is present
if (StringHelper.hasNoText(aEmailData.getSubject())) {
if (LOGGER.isWarnEnabled())
LOGGER.warn("Mail data has no subject: " + aEmailData + " - defaulting to " + DEFAULT_SUBJECT);
aEmailData.setSubject(DEFAULT_SUBJECT);
}
// Check if a body is present
if (StringHelper.hasNoText(aEmailData.getBody()))
if (LOGGER.isWarnEnabled())
LOGGER.warn("Mail data has no body: " + aEmailData);
if (bSendVendorOnlyMails) {
// Add special debug prefix
if (!StringHelper.startsWith(aEmailData.getSubject(), DEBUG_SUBJECT_PREFIX))
aEmailData.setSubject(DEBUG_SUBJECT_PREFIX + aEmailData.getSubject());
if (LOGGER.isInfoEnabled())
LOGGER.info("Sending only-to-vendor mail in debug version:\n" + aSMTPSettings + "\n" + aEmailData);
}
// Uses UTC timezone!
aEmailData.setSentDateTime(PDTFactory.getCurrentLocalDateTime());
try {
if (aSMTPQueue.queueObject(aEmailData).isSuccess()) {
bWasQueued = true;
++nQueuedMails;
}
// else an error message was already logged
} catch (final Exception ex) {
// Occurs if queue is already stopped
aException = ex;
}
} else {
aException = new MailSendException("Email cannot be queued because internal constraints are not fulfilled!");
}
if (!bWasQueued) {
// Mail was not queued - put in failed mail queue
aSMTPQueue.getFailedMailQueue().add(new FailedMailData(aSMTPSettings, aEmailData, new MailTransportError(aException)));
}
}
return nQueuedMails;
}
use of com.helger.smtp.data.IMutableEmailData in project ph-web by phax.
the class MailQueuePerSMTP method runAsync.
/**
* This is the callback to be invoked every time something is in the queue.
*
* @param aMessages
* The non-null and non-empty list of messages to be send
*/
public void runAsync(@Nullable final List<IMutableEmailData> aMessages) {
// Expect the worst
if (CollectionHelper.isNotEmpty(aMessages)) {
final ISMTPSettings aSettings = m_aTransport.getSMTPSettings();
try {
final int nMessages = aMessages.size();
if (LOGGER.isInfoEnabled())
LOGGER.info("Sending " + nMessages + " mail message" + (nMessages == 1 ? "" : "s") + "!");
// send messages
final ICommonsOrderedMap<IMutableEmailData, MailTransportError> aFailedMessages = m_aTransport.send(aMessages);
// handle failed messages
for (final Map.Entry<IMutableEmailData, MailTransportError> aEntry : aFailedMessages.entrySet()) m_aFailedMailQueue.add(new FailedMailData(aSettings, aEntry.getKey(), aEntry.getValue()));
} catch (final Exception ex) {
// No message specific error, but a settings specific error
if (LOGGER.isErrorEnabled())
LOGGER.error("Generic error sending mail: " + ex.getMessage(), ex.getCause());
// mark all mails as failed even though some may have been re-send
// already
final MailTransportError aError = new MailTransportError(ex);
for (final IMutableEmailData aMessage : aMessages) m_aFailedMailQueue.add(new FailedMailData(aSettings, aMessage, aError));
}
}
}
use of com.helger.smtp.data.IMutableEmailData in project ph-web by phax.
the class MailTransport method send.
/**
* Actually send the given array of MimeMessages via JavaMail.
*
* @param aAllMessages
* Email data objects to send. May be <code>null</code>.
* @return A non-<code>null</code> map of the failed messages
*/
@Nonnull
public ICommonsOrderedMap<IMutableEmailData, MailTransportError> send(@Nullable final Collection<IMutableEmailData> aAllMessages) {
final ICommonsOrderedMap<IMutableEmailData, MailTransportError> aFailedMessages = new CommonsLinkedHashMap<>();
if (aAllMessages != null) {
final ICommonsList<IMutableEmailData> aRemainingMessages = new CommonsArrayList<>(aAllMessages);
MailSendException aExceptionToBeRemembered = null;
try (final Transport aTransport = m_aSession.getTransport(m_bSMTPS ? SMTPS_PROTOCOL : SMTP_PROTOCOL)) {
// Add global listeners (if present)
for (final ConnectionListener aConnectionListener : EmailGlobalSettings.getAllConnectionListeners()) aTransport.addConnectionListener(aConnectionListener);
// Check if a detailed listener is present
final ICommonsList<IEmailDataTransportListener> aEmailDataTransportListeners = EmailGlobalSettings.getAllEmailDataTransportListeners();
// Connect
aTransport.connect(m_aSMTPSettings.getHostName(), m_aSMTPSettings.getPort(), m_aSMTPSettings.getUserName(), m_aSMTPSettings.getPassword());
// For all messages
for (final IMutableEmailData aEmailData : aAllMessages) {
final MimeMessage aMimeMessage = new MimeMessage(m_aSession);
try {
// convert from IEmailData to MimeMessage
MailConverter.fillMimeMessage(aMimeMessage, aEmailData, m_aSMTPSettings.getCharsetObj());
// Ensure a sent date is present
if (aMimeMessage.getSentDate() == null)
aMimeMessage.setSentDate(new Date());
// Get an explicitly specified message ID
final String sMessageID = aMimeMessage.getMessageID();
// This creates a new message ID (besides other things)
aMimeMessage.saveChanges();
if (sMessageID != null) {
// Preserve explicitly specified message id...
aMimeMessage.setHeader(HEADER_MESSAGE_ID, sMessageID);
}
aMimeMessage.setHeader(HEADER_X_MAILER, X_MAILER);
if (LOGGER.isInfoEnabled())
LOGGER.info("Delivering mail from " + Arrays.toString(aMimeMessage.getFrom()) + " to " + Arrays.toString(aMimeMessage.getAllRecipients()) + " with subject '" + aMimeMessage.getSubject() + "' and message ID '" + aMimeMessage.getMessageID() + "'");
// Main transmit - always throws an exception
aTransport.sendMessage(aMimeMessage, aMimeMessage.getAllRecipients());
throw new IllegalStateException("Never expected to come beyong sendMessage!");
} catch (final SendFailedException ex) {
if (EmailGlobalSettings.isDebugSMTP())
LOGGER.error("Error send mail - SendFailedException", ex);
/*
* Extract all addresses: the valid addresses to which the message
* was sent, the valid address to which the message was not sent and
* the invalid addresses
*/
final ICommonsSet<String> aValidSent = new CommonsHashSet<>(ex.getValidSentAddresses(), Address::toString);
final ICommonsSet<String> aValidUnsent = new CommonsHashSet<>(ex.getValidUnsentAddresses(), Address::toString);
final ICommonsSet<String> aInvalid = new CommonsHashSet<>(ex.getInvalidAddresses(), Address::toString);
final ICommonsList<MailSendDetails> aDetails = new CommonsArrayList<>();
Exception ex2;
MessagingException bex = ex;
while ((ex2 = bex.getNextException()) != null && ex2 instanceof MessagingException) {
if (ex2 instanceof SMTPAddressFailedException) {
final SMTPAddressFailedException ssfe = (SMTPAddressFailedException) ex2;
aDetails.add(new MailSendDetails(false, ssfe.getAddress().toString(), ssfe.getCommand(), ssfe.getMessage().trim(), ESMTPErrorCode.getFromIDOrDefault(ssfe.getReturnCode(), ESMTPErrorCode.FALLBACK)));
} else if (ex2 instanceof SMTPAddressSucceededException) {
final SMTPAddressSucceededException ssfe = (SMTPAddressSucceededException) ex2;
aDetails.add(new MailSendDetails(true, ssfe.getAddress().toString(), ssfe.getCommand(), ssfe.getMessage().trim(), ESMTPErrorCode.getFromIDOrDefault(ssfe.getReturnCode(), ESMTPErrorCode.FALLBACK)));
}
bex = (MessagingException) ex2;
}
// Map addresses to details
final ICommonsOrderedSet<MailSendDetails> aValidSentExt = new CommonsLinkedHashSet<>();
final ICommonsOrderedSet<MailSendDetails> aValidUnsentExt = new CommonsLinkedHashSet<>();
final ICommonsOrderedSet<MailSendDetails> aInvalidExt = new CommonsLinkedHashSet<>();
for (final MailSendDetails aFailure : aDetails) {
final String sAddress = aFailure.getAddress();
if (aValidSent.contains(sAddress))
aValidSentExt.add(aFailure);
else if (aValidUnsent.contains(sAddress))
aValidUnsentExt.add(aFailure);
else
aInvalidExt.add(aFailure);
}
final EmailDataTransportEvent aEvent = new EmailDataTransportEvent(m_aSMTPSettings, aEmailData, aMimeMessage, aValidSentExt, aValidUnsentExt, aInvalidExt);
if (aValidUnsent.isEmpty() && aInvalid.isEmpty() && aValidSent.isNotEmpty()) {
// Message delivered
for (final IEmailDataTransportListener aEmailDataTransportListener : aEmailDataTransportListeners) aEmailDataTransportListener.messageDelivered(aEvent);
// Remove message from list of remaining
STATS_SEND_SUCCESS.increment();
} else {
// Message not delivered
for (final IEmailDataTransportListener aEmailDataTransportListener : aEmailDataTransportListeners) aEmailDataTransportListener.messageNotDelivered(aEvent);
// Sending exactly this message failed
aFailedMessages.put(aEmailData, new MailTransportError(ex, aDetails));
STATS_SEND_FAILURE.increment();
}
// Remove message from list of remaining as we put it in the
// failed message list manually in case of error
aRemainingMessages.remove(aEmailData);
} catch (final MessagingException ex) {
if (EmailGlobalSettings.isDebugSMTP())
LOGGER.error("Error send mail - MessagingException", ex);
final ICommonsOrderedSet<MailSendDetails> aInvalid = new CommonsLinkedHashSet<>();
final Consumer<IEmailAddress> aConsumer = a -> aInvalid.add(new MailSendDetails(false, a.getAddress(), "<generic error>", ex.getMessage(), ESMTPErrorCode.FALLBACK));
aEmailData.to().forEach(aConsumer);
aEmailData.cc().forEach(aConsumer);
aEmailData.bcc().forEach(aConsumer);
final EmailDataTransportEvent aEvent = new EmailDataTransportEvent(m_aSMTPSettings, aEmailData, aMimeMessage, new CommonsArrayList<>(), new CommonsArrayList<>(), aInvalid);
// Message not delivered
for (final IEmailDataTransportListener aEmailDataTransportListener : aEmailDataTransportListeners) aEmailDataTransportListener.messageNotDelivered(aEvent);
// Sending exactly this message failed
aFailedMessages.put(aEmailData, new MailTransportError(ex));
// Remove message from list of remaining as we put it in the
// failed message list manually
aRemainingMessages.remove(aEmailData);
STATS_SEND_FAILURE.increment();
}
}
// for all messages
} catch (final AuthenticationFailedException ex) {
// problem with the credentials
aExceptionToBeRemembered = new MailSendException("Mail server authentication failed", ex);
} catch (final MessagingException ex) {
if (WebExceptionHelper.isServerNotReachableConnection(ex.getCause()))
aExceptionToBeRemembered = new MailSendException("Failed to connect to mail server: " + ex.getCause().getMessage());
else
aExceptionToBeRemembered = new MailSendException("Mail server connection failed", ex);
} catch (final Exception ex) {
// E.g. IllegalState from SMTPTransport ("Not connected")
aExceptionToBeRemembered = new MailSendException("Internal error sending mail", ex);
} finally {
// Was any message not sent
if (aRemainingMessages.isNotEmpty()) {
if (aExceptionToBeRemembered == null)
aExceptionToBeRemembered = new MailSendException("Internal error - messages are remaining but no Exception occurred!");
for (final IMutableEmailData aRemainingMessage : aRemainingMessages) aFailedMessages.put(aRemainingMessage, new MailTransportError(aExceptionToBeRemembered));
}
}
}
return aFailedMessages;
}
use of com.helger.smtp.data.IMutableEmailData in project ph-web by phax.
the class MailAPITest method testStopImmediately.
@Ignore("to avoid spamming my mailbox")
@Test
public void testStopImmediately() {
/*
* This file might not be present, as it contains the real-life SMTP
* settings. It should reside in src/test/resource and is SVN ignored by
* name
*/
final IReadableResource aRes = new ClassPathResource("smtp-settings.xml");
if (aRes.exists()) {
final SMTPSettings aSMTPSettings = MicroTypeConverter.convertToNative(MicroReader.readMicroXML(aRes).getDocumentElement(), SMTPSettings.class);
final IMutableEmailData aMailData = new EmailData(EEmailType.TEXT);
aMailData.to().add(new EmailAddress("ph@helger.com"));
aMailData.setFrom(new EmailAddress("auto@helger.com"));
aMailData.setSubject("JÜnit test with späcial käräktärs");
aMailData.setBody("Hi there\nLine 2\n4 special chars: äöüß\n123456789\nBest regards: ph-smtp");
final ICommonsList<IMutableEmailData> aMails = new CommonsArrayList<>();
for (int i = 0; i < 10; ++i) aMails.add(aMailData);
MailAPI.queueMails(aSMTPSettings, aMails);
ThreadHelper.sleep(20);
// Stop immediately
MailAPI.stop(true);
}
}
use of com.helger.smtp.data.IMutableEmailData in project ph-web by phax.
the class FailedMailDataMicroTypeConverter method convertToNative.
@Nullable
public FailedMailData convertToNative(@Nonnull final IMicroElement eFailedMail) {
final String sID = eFailedMail.getAttributeValue(ATTR_ID);
if (sID == null) {
LOGGER.error("Failed to read ID");
return null;
}
// Read error date/time
final String sErrorDT = eFailedMail.getAttributeValue(ATTR_ERRORDT);
if (sErrorDT == null) {
LOGGER.error("Failed to read error date/time");
return null;
}
LocalDateTime aErrorDT = PDTWebDateHelper.getLocalDateTimeFromXSD(sErrorDT);
if (aErrorDT == null)
aErrorDT = TypeConverter.convert(sErrorDT, LocalDateTime.class);
if (aErrorDT == null) {
if (LOGGER.isErrorEnabled())
LOGGER.error("Failed to parse error date '" + sErrorDT + "'");
return null;
}
// read original sent date/time
final String sOriginalSentDT = eFailedMail.getAttributeValue(ATTR_ORIGINALSENT_DT);
LocalDateTime aOriginalSentDT = null;
if (sOriginalSentDT != null) {
aOriginalSentDT = PDTWebDateHelper.getLocalDateTimeFromXSD(sOriginalSentDT);
if (aOriginalSentDT == null)
aOriginalSentDT = TypeConverter.convert(sOriginalSentDT, LocalDateTime.class);
}
// SMTP settings
final IMicroElement eSMTPSettings = eFailedMail.getFirstChildElement(ELEMENT_SMTP_SETTINGS);
if (eSMTPSettings == null) {
LOGGER.error("Failed to get child element of SMTP settings!");
return null;
}
final ISMTPSettings aSMTPSettings = MicroTypeConverter.convertToNative(eSMTPSettings, SMTPSettings.class);
// email data (may be null)
final IMicroElement eEmailData = eFailedMail.getFirstChildElement(ELEMENT_EMAIL_DATA);
final IMutableEmailData aEmailData = MicroTypeConverter.convertToNative(eEmailData, EmailData.class);
// error message
final String sErrorMessage = MicroHelper.getChildTextContent(eFailedMail, ELEMENT_ERROR_MSG);
final Exception aException = StringHelper.hasNoText(sErrorMessage) ? null : new Exception(sErrorMessage);
MailTransportError aError = null;
if (aException != null) {
final ICommonsList<MailSendDetails> aDetails = new CommonsArrayList<>();
for (final IMicroElement eDetails : eFailedMail.getAllChildElements(ELEMENT_DETAILS)) {
final boolean bAddressValid = StringParser.parseBool(eDetails.getAttributeValue(ATTR_ADDRESS_VALID));
final String sAddress = eDetails.getAttributeValue(ATTR_ADDRESS);
final String sCommand = eDetails.getAttributeValue(ATTR_COMMAND);
final String sDetailsErrorMessage = eDetails.getAttributeValue(ATTR_ERROR_MESSAGE);
final int nErrorCode = StringParser.parseInt(eDetails.getAttributeValue(ATTR_ERROR_CODE), -1);
final ESMTPErrorCode eErrorCode = ESMTPErrorCode.getFromIDOrDefault(nErrorCode, ESMTPErrorCode.FALLBACK);
aDetails.add(new MailSendDetails(bAddressValid, sAddress, sCommand, sDetailsErrorMessage, eErrorCode));
}
aError = new MailTransportError(aException, aDetails);
}
return new FailedMailData(sID, aErrorDT, aSMTPSettings, aOriginalSentDT, aEmailData, aError);
}
Aggregations