//
// ERMailer.java
// Project ERMailer
//
// Created by max on Tue Oct 22 2002
//
package er.javamail.mailer;
import java.io.File;
import java.util.Enumeration;
import javax.mail.MessagingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.eoaccess.EOGeneralAdaptorException;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSTimestamp;
import er.corebusinesslogic.ERCMailMessage;
import er.corebusinesslogic.ERCMailState;
import er.corebusinesslogic.ERCMessageAttachment;
import er.corebusinesslogic.ERCoreBusinessLogic;
import er.extensions.eof.ERXEC;
import er.extensions.eof.ERXFetchSpecificationBatchIterator;
import er.extensions.foundation.ERXProperties;
import er.javamail.ERMailDelivery;
import er.javamail.ERMailDeliveryHTML;
import er.javamail.ERMailDeliveryPlainText;
import er.javamail.ERMailFileAttachment;
/**
* Mailer bridge class. Used to pull mail out of the
* ERMailMessage entity and send it via the ERJavaMail
* framework for sending mail.
*
* @property er.javamail.mailer.ERMailer.WarnOnGeneralAdaptorExceptionLockingMessage
* @property er.javamail.mailer.ERMailer.ShouldDeleteSentMail
*/
public class ERMailer {
// ===========================================================================
// Class Constant(s)
// ---------------------------------------------------------------------------
private final static Logger log = LoggerFactory.getLogger(ERMailer.class);
// ===========================================================================
// Class Variable(s)
// ---------------------------------------------------------------------------
/** holds a reference to the shared instance */
protected static ERMailer instance;
protected static Factory factory;
private static final boolean _warnOnGeneralAdaptorExceptionLockingMessage =
ERXProperties.booleanForKeyWithDefault("er.javamail.mailer.ERMailer.WarnOnGeneralAdaptorExceptionLockingMessage", true);
// ===========================================================================
// Class Method(s)
// ---------------------------------------------------------------------------
/**
* Gets the current factory. If the factory is unset, sets the factory to the default
* factory.
*
* @return the factory
*/
public static Factory factory() {
if ( factory == null )
factory = new DefaultFactory();
return factory;
}
/**
* Sets the factory.
*
* @param value new factory value
*/
public static void setFactory(Factory value) {
factory = value;
}
/**
* Instantiates a new mailer instance using the factory and returns it.
*
* @return a new mailer instance.
*/
public static ERMailer newMailer() {
return factory().newMailer();
}
protected static boolean shouldDeleteSentMail() {
return ERXProperties.booleanForKeyWithDefault("er.javamail.mailer.ERMailer.ShouldDeleteSentMail", true);
}
/**
* Gets the shared mailer instance.
* @return mailer singleton
*/
public static ERMailer instance() {
if ( instance == null )
instance = newMailer();
return instance;
}
// ===========================================================================
// Instance Variable(s)
// ---------------------------------------------------------------------------
/** Caches the message title prefix */
protected String messageTitlePrefix;
// ===========================================================================
// Instance Method(s)
// ---------------------------------------------------------------------------
/**
* Fetches all mail that is ready to
* be sent from the ERMailMessage table
* and sends the message using the
* ERJavaMail framework for sending
* messages.
*/
public void processOutgoingMail() {
log.debug("Starting outgoing mail processing.");
ERXFetchSpecificationBatchIterator iterator = ERCMailMessage.mailMessageClazz().batchIteratorForUnsentMessages();
EOEditingContext ec = ERXEC.newEditingContext();
iterator.setEditingContext(ec);
ec.lock();
try {
iterator.batchCount();
} finally {
ec.unlock();
}
ec.dispose();
while (iterator.hasNextBatch()) {
EOEditingContext temp = ERXEC.newEditingContext();
temp.lock();
try {
iterator.setEditingContext(temp);
sendMailMessages(iterator.nextBatch());
} finally {
temp.unlock();
}
temp.dispose();
}
log.debug("Done outgoing mail processing.");
}
/**
* Sends an array of ERCMailMessage objects.
* @param mailMessages array of messages to send
*/
public void sendMailMessages(NSArray mailMessages) {
if (mailMessages.count() > 0) {
log.info("Sending {} mail message(s).", mailMessages.count());
for (Enumeration messageEnumerator = mailMessages.objectEnumerator();
messageEnumerator.hasMoreElements();) {
ERCMailMessage mailMessage = (ERCMailMessage)messageEnumerator.nextElement();
if( !mailMessage.isReadyToSendState() ) { //due to the operation of the batch iterator, we may pull records that have already been sent
continue;
}
log.debug("Sending mail message: {}", mailMessage);
try {
ERMailDelivery delivery = createMailDeliveryForMailMessage(mailMessage);
if (delivery != null) {
mailMessage.setState(ERCMailState.PROCESSING_STATE);
mailMessage.editingContext().saveChanges(); // This will throw if optimistic locking occurs
delivery.sendMail(true);
mailMessage.setState(ERCMailState.SENT_STATE);
mailMessage.setDateSent(new NSTimestamp());
if (shouldDeleteSentMail()) {
if (mailMessage.shouldArchiveSentMailAsBoolean()) {
mailMessage.archive();
}
// FIXME: Nasty stack overflow bug
if (!mailMessage.hasAttachments()) {
mailMessage.editingContext().deleteObject(mailMessage);
}
}
} else {
log.warn("Unable to create mail delivery for mail message: {}", mailMessage);
}
} catch (EOGeneralAdaptorException ge) {
if ( _warnOnGeneralAdaptorExceptionLockingMessage )
log.warn("Caught general adaptor exception, reverting context. Might be running multiple mailers", ge);
mailMessage.editingContext().revert();
} catch (Throwable e) {
if (e instanceof NSForwardException)
e = ((NSForwardException)e).originalException();
log.warn("Caught exception when sending mail.", e);
log.warn("Message trying to send: {} pk: {}", mailMessage, mailMessage.primaryKey());
// ENHANCEME: Need to implement a waiting state to retry sending mails.
mailMessage.setState(ERCMailState.EXCEPTION_STATE);
mailMessage.setExceptionReason(e.getMessage());
// Report the mailing error
ERCoreBusinessLogic.sharedInstance().reportException(e, new NSDictionary(mailMessage.snapshot(),
"Mail Message Snapshot"));
} finally {
// The editingcontext will not have any changes if an optimistic error occurred
if (mailMessage.editingContext().hasChanges()) {
try {
mailMessage.editingContext().saveChanges();
} catch (RuntimeException runtime) {
log.error("RuntimeException during save changes!", runtime);
throw runtime;
}
}
}
}
}
}
/**
* Creates a ERMailDelivery for a given
* MailMessage.
* @param message mail message
* @return a mail delevery object
*/
// ENHANCEME: Not handling double byte (Japanese) language
public ERMailDelivery createMailDeliveryForMailMessage(ERCMailMessage message) throws MessagingException {
ERMailDelivery mail = null;
if (message.text() != null) {
mail = ERMailDeliveryHTML.newMailDelivery();
((ERMailDeliveryHTML)mail).setHTMLContent(message.text());
if (message.plainText() != null)
((ERMailDeliveryHTML)mail).setHiddenPlainTextContent(message.plainText());
} else {
mail = new ERMailDeliveryPlainText();
((ERMailDeliveryPlainText)mail).setTextContent(message.plainText());
}
// Add all of the addresses
mail.setFromAddress(message.fromAddress());
if (message.replyToAddress() != null)
mail.setReplyToAddress(message.replyToAddress());
mail.setToAddresses(message.toAddressesAsArray());
if (message.ccAddressesAsArray().count() > 0)
mail.setCCAddresses(message.ccAddressesAsArray());
if (message.bccAddressesAsArray().count() > 0)
mail.setBCCAddresses(message.bccAddressesAsArray());
// Set the xMailer if one is specified
// Note (tuscland): setXMailerHeader has a higher precedence over
// System property er.javamail.XMailerHeader
if (message.xMailer() != null)
mail.setXMailerHeader(message.xMailer());
// Set the content
mail.setSubject(messageTitlePrefix() + message.title());
if (message.hasAttachments()) {
for (Enumeration attachmentEnumerator = message.attachments().objectEnumerator(); attachmentEnumerator.hasMoreElements();) {
File fileAttachment = ((ERCMessageAttachment)attachmentEnumerator.nextElement()).file();
mail.addAttachment(new ERMailFileAttachment(fileAttachment.getName(), null, fileAttachment));
}
}
return mail;
}
/**
* The message title prefix is used to distiguish emails generated in different environments.
* @return message title prefix
*/
public String messageTitlePrefix() {
if (messageTitlePrefix == null) {
messageTitlePrefix = ERCoreBusinessLogic.staticStoredValueForKey("ERMailTitleEnvironmentPrefix");
if (messageTitlePrefix == null) {
messageTitlePrefix = "";
}
}
return messageTitlePrefix;
}
// ===========================================================================
// Factory-related things
// ---------------------------------------------------------------------------
public static interface Factory {
/**
* Vends new instances of a mailer. This is primarily used to set the static instance
* of ERMailer.
*
* @return A new instance of an ERMailer or a subclass.
*/
public ERMailer newMailer();
}
/**
* Default factory. Just vends back an ERMailer instance.
*/
public static class DefaultFactory implements Factory {
public ERMailer newMailer() {
return new ERMailer();
}
}
}