package org.subethamail.core.post;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.ejb.EJBException;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import lombok.extern.java.Log;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.subethamail.common.SubEthaMessage;
import org.subethamail.core.admin.SiteSettings;
import org.subethamail.core.admin.i.Eegor;
import org.subethamail.core.admin.i.Encryptor;
import org.subethamail.core.post.i.Constant;
import org.subethamail.core.post.i.MailType;
import org.subethamail.core.util.OwnerAddress;
import org.subethamail.core.util.SubEtha;
import org.subethamail.core.util.SubEthaEntityManager;
import org.subethamail.core.util.VERPAddress;
import org.subethamail.entity.EmailAddress;
import org.subethamail.entity.Mail;
import org.subethamail.entity.MailingList;
import org.subethamail.entity.Person;
import org.subethamail.entity.Subscription;
import org.subethamail.entity.SubscriptionHold;
/**
* Implementation of the PostOffice interface.
*
* @author Jeff Schnitzer
* @author Scott Hernandez
*/
@Stateless(name="PostOffice")
@Log
public class PostOfficeBean implements PostOffice
{
/** */
@Inject @OutboundMTA Session mailSession;
/** */
@Inject Encryptor encryptor;
/** */
@Inject Eegor brainBringer;
/** */
@Inject @SubEtha SubEthaEntityManager em;
/** */
@Inject SiteSettings settings;
/**
* Builds a message from a velocity template, context, and some
* information about sender and recipient.
*/
class MessageBuilder
{
SubEthaMessage message;
InternetAddress toAddress;
InternetAddress fromAddress;
String senderEmail;
/** */
public MessageBuilder(MailType kind, VelocityContext vctx)
{
StringWriter writer = new StringWriter(4096);
try
{
Velocity.mergeTemplate(kind.getTemplate(), "UTF-8", vctx, writer);
}
catch (Exception ex)
{
LogRecord logRecord=new LogRecord(Level.SEVERE,"Error merging {0}");;
logRecord.setParameters(new Object[]{kind.getTemplate()});
logRecord.setThrown(ex);
log.log(logRecord);
throw new EJBException(ex);
}
String mailSubject = (String)vctx.get("subject");
String mailBody = writer.toString();
// If in dev mode, annotate the subject for unit tests
if (brainBringer.isTestModeEnabled())
mailSubject = kind.name() + " " + mailSubject;
try
{
this.message = new SubEthaMessage(mailSession);
this.message.setSubject(mailSubject);
this.message.setText(mailBody);
}
catch (MessagingException ex) { throw new RuntimeException(ex); }
}
/** */
public void setTo(EmailAddress to)
{
to.bounceDecay();
try
{
this.toAddress = new InternetAddress(to.getId(), to.getPerson().getName());
}
catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); }
}
/** */
public void setTo(String to)
{
try
{
this.toAddress = new InternetAddress(to);
}
catch (AddressException ex) { throw new RuntimeException(ex); }
}
/** Must call setTo first */
public void setFrom(MailingList list)
{
if (this.toAddress == null)
throw new IllegalStateException("Must call setTo() first");
// Set the list owner as the pretty from field
String ownerAddress = OwnerAddress.makeOwner(list.getEmail());
try
{
this.fromAddress = new InternetAddress(ownerAddress, list.getName());
}
catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); }
// Set up the VERP bounce address as the envelope sender
byte[] token = encryptor.encryptString(this.toAddress.getAddress());
this.senderEmail = VERPAddress.encodeVERP(list.getEmail(), token);
}
/** Must call setTo first */
public void setFrom(InternetAddress from)
{
if (this.toAddress == null)
throw new IllegalStateException("Must call setTo() first");
this.fromAddress = from;
}
/** Must call setTo first */
public void setFrom(String from)
{
if (this.toAddress == null)
throw new IllegalStateException("Must call setTo() first");
try
{
this.fromAddress = new InternetAddress(from);
}
catch (AddressException ex) { throw new RuntimeException(ex); }
}
/**
* @return the message we have built.
*/
public SubEthaMessage getMessage() throws MessagingException
{
this.message.setRecipient(Message.RecipientType.TO, this.toAddress);
this.message.setFrom(this.fromAddress);
this.message.setEnvelopeFrom(this.senderEmail);
// This minimizes the likelyhood of getting autoreplies
this.message.setHeader("Precedence", "junk");
return this.message;
}
/**
* Sends the message through JavaMail
*/
public void send()
{
try
{
Transport.send(this.getMessage());
}
catch (MessagingException ex) { throw new RuntimeException(ex); }
}
}
/**
* Maybe modifies the token with developer/test information which can be picked out
* by the unit tester.
*/
protected String token(String tok)
{
return !this.brainBringer.isTestModeEnabled() ? tok : Constant.DEBUG_TOKEN_BEGIN + tok + Constant.DEBUG_TOKEN_END;
}
/**
* @see PostOffice#sendPassword(EmailAddress)
*/
public void sendPassword(EmailAddress addy)
{
log.log(Level.FINE,"Sending password for {0}", addy.getId());
VelocityContext vctx = new VelocityContext();
vctx.put("addy", addy);
if (addy.getPerson().getSubscriptions().size() == 1)
{
// If only one list, make it appear that mail comes from there
Subscription sub = addy.getPerson().getSubscriptions().values().iterator().next();
vctx.put("url", sub.getList().getUrlBase());
MessageBuilder builder = new MessageBuilder(MailType.FORGOT_PASSWORD, vctx);
builder.setTo(addy);
builder.setFrom(sub.getList());
builder.send();
}
else
{
URL url = this.settings.getDefaultSiteUrl();
vctx.put("url", url.toString());
MessageBuilder builder = new MessageBuilder(MailType.FORGOT_PASSWORD, vctx);
builder.setTo(addy);
builder.setFrom(this.settings.getPostmaster());
builder.send();
}
}
/**
* @see PostOffice#sendConfirmSubscribeToken(MailingList, String, String)
*/
public void sendConfirmSubscribeToken(MailingList list, String email, String token)
{
log.log(Level.FINE,"Sending subscribe token to {0}", email);
VelocityContext vctx = new VelocityContext();
vctx.put("token", this.token(token));
vctx.put("email", email);
vctx.put("list", list);
MessageBuilder builder = new MessageBuilder(MailType.CONFIRM_SUBSCRIBE, vctx);
builder.setTo(email);
builder.setFrom(list);
builder.send();
}
/**
* @see PostOffice#sendSubscribed(MailingList, Person, EmailAddress)
*/
public void sendSubscribed(MailingList relevantList, Person who, EmailAddress deliverTo)
{
log.log(Level.FINE,"Sending welcome to list msg to {0}", who);
String email;
if (deliverTo != null)
email = deliverTo.getId();
else
email = who.getEmailAddresses().values().iterator().next().getId();
VelocityContext vctx = new VelocityContext();
vctx.put("list", relevantList);
vctx.put("person", who);
vctx.put("email", email);
MessageBuilder builder = new MessageBuilder(MailType.YOU_SUBSCRIBED, vctx);
builder.setTo(email);
builder.setFrom(relevantList);
builder.send();
}
/**
* @see PostOffice#sendOwnerNewMailingList(MailingList, EmailAddress)
*/
public void sendOwnerNewMailingList(MailingList relevantList, EmailAddress address)
{
log.log(Level.FINE,"Sending notification of new mailing list {0} to owner {1}", new Object[]{relevantList, address});
VelocityContext vctx = new VelocityContext();
vctx.put("addy", address);
vctx.put("list", relevantList);
MessageBuilder builder = new MessageBuilder(MailType.NEW_MAILING_LIST, vctx);
builder.setTo(address);
builder.setFrom(relevantList);
builder.send();
}
/**
* @see PostOffice#sendAddEmailToken(Person, String, String)
*/
public void sendAddEmailToken(Person me, String email, String token)
{
log.log(Level.FINE,"Sending add email token to {0}", email);
VelocityContext vctx = new VelocityContext();
vctx.put("token", this.token(token));
vctx.put("email", email);
vctx.put("person", me);
if (me.getSubscriptions().size() == 1)
{
// If only one list, make it appear to come from that list
Subscription sub = me.getSubscriptions().values().iterator().next();
vctx.put("url", sub.getList().getUrlBase());
MessageBuilder builder = new MessageBuilder(MailType.CONFIRM_EMAIL, vctx);
builder.setTo(email);
builder.setFrom(sub.getList());
builder.send();
}
else
{
URL url = this.settings.getDefaultSiteUrl();
vctx.put("url", url.toString());
MessageBuilder builder = new MessageBuilder(MailType.CONFIRM_EMAIL, vctx);
builder.setTo(email);
builder.setFrom(this.settings.getPostmaster());
builder.send();
}
}
/*
* (non-Javadoc)
* @see org.subethamail.core.post.PostOffice#sendModeratorSubscriptionHeldNotice(org.subethamail.entity.EmailAddress, org.subethamail.entity.SubscriptionHold)
*/
public void sendModeratorSubscriptionHeldNotice(EmailAddress moderator, SubscriptionHold hold)
{
log.log(Level.FINE,"Sending sub held notice for {0} to {1}", new Object[]{hold, moderator});
VelocityContext vctx = new VelocityContext();
vctx.put("hold", hold);
vctx.put("moderator", moderator);
MessageBuilder builder = new MessageBuilder(MailType.SUBSCRIPTION_HELD, vctx);
builder.setTo(moderator);
builder.setFrom(hold.getList());
builder.send();
}
/*
* (non-Javadoc)
* @see org.subethamail.core.post.PostOffice#sendPosterMailHoldNotice(org.subethamail.entity.MailingList, java.lang.String, org.subethamail.entity.Mail, java.lang.String)
*/
public void sendPosterMailHoldNotice(MailingList relevantList, String posterEmail, Mail mail, String holdMsg)
{
log.log(Level.FINE,"Sending mail held notice to {0}", posterEmail);
VelocityContext vctx = new VelocityContext();
vctx.put("list", relevantList);
vctx.put("mail", mail);
vctx.put("holdMsg", holdMsg);
vctx.put("email", posterEmail);
MessageBuilder builder = new MessageBuilder(MailType.YOUR_MAIL_HELD, vctx);
builder.setTo(posterEmail);
builder.setFrom(relevantList);
builder.send();
}
/*
* (non-Javadoc)
* @see org.subethamail.core.post.PostOffice#sendModeratorMailHoldNotice(org.subethamail.entity.EmailAddress, org.subethamail.entity.MailingList, org.subethamail.entity.Mail, org.subethamail.common.SubEthaMessage, java.lang.String)
*/
public void sendModeratorMailHoldNotice(EmailAddress moderator, MailingList relevantList, Mail mail, SubEthaMessage msg, String holdMsg)
{
log.log(Level.FINE,"Sending mail held notice to moderator {0}", moderator);
VelocityContext vctx = new VelocityContext();
vctx.put("list", relevantList);
vctx.put("mail", mail);
vctx.put("msg", msg);
vctx.put("holdMsg", holdMsg);
vctx.put("moderator", moderator);
MessageBuilder builder = new MessageBuilder(MailType.MAIL_HELD, vctx);
builder.setTo(moderator);
builder.setFrom(relevantList);
builder.send();
}
/*
* (non-Javadoc)
* @see org.subethamail.core.post.PostOffice#sendModeratorSubscriptionNotice(org.subethamail.entity.EmailAddress, org.subethamail.entity.Subscription, boolean)
*/
public void sendModeratorSubscriptionNotice(EmailAddress moderator, Subscription sub, boolean unsub)
{
log.log(Level.FINE,"Sending {0} notice for {1} to {2}", new Object[]{(unsub ? "unsub" : "sub"), sub, moderator});
VelocityContext vctx = new VelocityContext();
vctx.put("sub", sub);
vctx.put("moderator", moderator);
vctx.put("unsub", unsub);
MessageBuilder builder = new MessageBuilder(MailType.PERSON_SUBSCRIBED, vctx);
builder.setTo(moderator);
builder.setFrom(sub.getList());
builder.send();
}
}