package hirondelle.web4j.webmaster;
import hirondelle.web4j.model.AppException;
import hirondelle.web4j.readconfig.Config;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.WebUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
/**
Default implementation of {@link Emailer}.
<P>Uses these <tt>init-param</tt> settings in <tt>web.xml</tt>:
<ul>
<li><tt>Webmaster</tt> : the email address of the webmaster.
<li><tt>MailServerConfig</tt> : configuration data to be passed to the mail server, as a list of name=value pairs.
Each name=value pair appears on a single line by itself. Used for <tt>mail.host</tt> settings, and so on.
The special value <tt>NONE</tt> indicates that emails are suppressed, and will not be sent.
<li><tt>MailServerCredentials</tt> : user name and password for access to the outgoing mail server.
The user name is separated from the password by a pipe character '|'.
The special value <tt>NONE</tt> means that no credentials are needed (often the case when the wep app
and the outgoing mail server reside on the same network).
</ul>
<P>Example <tt>web.xml</tt> settings, using a Gmail account:
<PRE> <init-param>
<param-name>Webmaster</param-name>
<param-value>myaccount@gmail.com</param-value>
</init-param>
<init-param>
<param-name>MailServerConfig</param-name>
<param-value>
mail.smtp.host=smtp.gmail.com
mail.smtp.auth=true
mail.smtp.port=465
mail.smtp.socketFactory.port=465
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
</param-value>
</init-param>
<init-param>
<param-name>MailServerCredentials</param-name>
<param-value>myaccount@gmail.com|mypassword</param-value>
</init-param>
</pre>
*/
public final class EmailerImpl implements Emailer {
public void sendFromWebmaster(List<String> aToAddresses, String aSubject, String aBody) throws AppException {
if (isMailEnabled()) {
validateState(getWebmasterEmailAddress(), aToAddresses, aSubject, aBody);
fLogger.fine("Sending email using request thread.");
sendEmail(getWebmasterEmailAddress(), aToAddresses, aSubject, aBody);
}
else {
fLogger.fine("Mailing is disabled, since mail server is configured as " + Util.quote(Config.NONE));
}
}
// PRIVATE
private Config fConfig = new Config();
private static final Logger fLogger = Util.getLogger(EmailerImpl.class);
private boolean isMailEnabled() {
return fConfig.isEnabled(fConfig.getMailServerConfig());
}
private boolean areCredentialsEnabled() {
return fConfig.isEnabled(fConfig.getMailServerCredentials());
}
/** Return the mail server config in the form of a Properties object. */
private Properties getMailServerConfigProperties() {
Properties result = new Properties();
String rawValue = fConfig.getMailServerConfig();
/* Example data: mail.smtp.host = smtp.blah.com */
if(Util.textHasContent(rawValue)){
List<String> lines = getAsLines(rawValue);
for(String line : lines){
int delimIdx = line.indexOf("=");
String name = line.substring(0,delimIdx);
String value = line.substring(delimIdx+1);
if(isMissing(name) || isMissing(value)){
throw new RuntimeException(
"This line for the MailServerConfig setting in web.xml does not have the expected form: " + Util.quote(line)
);
}
result.put(name.trim(), value.trim());
}
}
return result;
}
private List<String> getAsLines(String aRawValue){
List<String> result = new ArrayList<String>();
StringTokenizer parser = new StringTokenizer(aRawValue, "\n\r");
while ( parser.hasMoreTokens() ) {
result.add( parser.nextToken().trim() );
}
return result;
}
private static boolean isMissing(String aText){
return ! Util.textHasContent(aText);
}
private String getWebmasterEmailAddress() {
return fConfig.getWebmaster();
}
private void validateState(String aFrom, List<String> aToAddresses, String aSubject, String aBody) throws AppException {
AppException ex = new AppException();
if (!WebUtil.isValidEmailAddress(aFrom)) {
ex.add("From-Address is not a valid email address.");
}
if (!Util.textHasContent(aSubject)) {
ex.add("Email subject has no content.");
}
if (!Util.textHasContent(aBody)) {
ex.add("Email body has no content.");
}
if (aToAddresses.isEmpty()){
ex.add("To-Address is empty.");
}
for(String email: aToAddresses){
if (!WebUtil.isValidEmailAddress(email)) {
ex.add("To-Address is not a valid email address: " + Util.quote(email));
}
}
if (ex.isNotEmpty()) {
fLogger.severe("Cannot send email : " + ex);
throw ex;
}
}
private void sendEmail(String aFrom, List<String> aToAddresses, String aSubject, String aBody) throws AppException {
fLogger.fine("Sending mail from " + Util.quote(aFrom));
fLogger.fine("Sending mail to " + Util.quote(aToAddresses));
Properties props = getMailServerConfigProperties();
//fLogger.fine("Properties: " + props);
try {
Authenticator auth = getAuthenticator();
//fLogger.fine("Authenticator: " + auth);
Session session = Session.getDefaultInstance(props, auth);
//session.setDebug(true);
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(aFrom));
for(String toAddr: aToAddresses){
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toAddr));
}
message.setSubject(aSubject);
message.setText(aBody);
Transport.send(message); // thread-safe? throttling makes the question irrelevant
}
catch (Throwable ex) {
fLogger.severe("CANNOT SEND EMAIL: " + ex);
throw new AppException("Cannot send email", ex);
}
fLogger.fine("Mail is sent.");
}
private Authenticator getAuthenticator(){
Authenticator result = null;
if( areCredentialsEnabled() ){
result = new SMTPAuthenticator();
}
return result;
}
private static final class SMTPAuthenticator extends Authenticator {
SMTPAuthenticator() {}
public PasswordAuthentication getPasswordAuthentication() {
PasswordAuthentication result = null;
/** Format is pipe separated : bob|passwd. */
String rawValue = new Config().getMailServerCredentials();
int delimIdx = rawValue.indexOf("|");
if(delimIdx != -1){
String userName = rawValue.substring(0,delimIdx);
String password = rawValue.substring(delimIdx+1);
result = new PasswordAuthentication(userName, password);
}
else {
throw new RuntimeException("Missing pipe separator between user name and password: " + rawValue);
}
return result;
}
}
}