/**
* Copyright (c) 2009 Juwi MacMillan Group GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
*/
package org.tizzit.util.mail;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import javax.mail.Address;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.Message.RecipientType;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author <a href="mailto:christiane.hausleiter@juwimm.com">Christiane Hausleiter</a>
*
*/
public class Mail {
private static Log log = LogFactory.getLog(Mail.class);
private MimeMessage message = null;
private String encoding = "UTF-8";
private String messageText = null;
private ArrayList<MimeBodyPart> attachments = null;
private Hashtable<String, String> tempFileNameMappings = null;
/**
* The value constructor initializes the instance.
*
* @param mailDS the datasource to use
*/
public Mail(String mailDS) {
try {
Session session = (Session) new InitialContext().lookup(mailDS);
if (session == null) {
throw new IllegalArgumentException("session could not be initialized with mailDS '" + mailDS + "'");
}
initializeMail(session);
} catch (NamingException exception) {
log.error(exception);
}
}
/**
* For testing purposes: Value constructor that creates a mail and its session based
* on the specified properties.
*
* @param testProperties {@link Properties} containing at least a valid entry for "mail.host"
*/
public Mail(Properties testProperties) {
Session session = Session.getDefaultInstance(testProperties);
if (session == null) {
throw new IllegalArgumentException("session could not be initialized with specified properties");
}
initializeMail(session);
}
private void initializeMail(Session session) {
this.message = new MimeMessage(session);
this.attachments = new ArrayList<MimeBodyPart>();
this.tempFileNameMappings = new Hashtable<String, String>();
}
/**
* Changes the mail's encoding from default (UTF-8) to ISO-8859-1.
*/
public void setEncodingToISO() {
this.encoding = "ISO-8859-1";
}
/**
* Sets the mail's sender.
*
* @param from the sender
*/
public void setFrom(String from) {
try {
this.message.setFrom(new InternetAddress(from));
} catch (MessagingException exception) {
log.error(exception);
}
}
/**
* Returns the mail's sender.
*
* @return the mail's sender
*/
public String getFrom() {
try {
return this.message.getFrom()[0].toString();
} catch (MessagingException exception) {
log.error(exception);
return "";
}
}
/**
* Sets the mail's receiver(s).
*
* @param to the mail's receiver(s)
*/
public void setTo(String[] to) {
if (to != null) {
try {
for (int i = 0; i < to.length; i++) {
this.message.addRecipient(RecipientType.TO, new InternetAddress(to[i]));
}
} catch (MessagingException exception) {
log.error(exception);
}
}
}
/**
* Adds one receiver.
*
* @param to another mail's receiver
*/
public void addTo(String to) {
if (to != null) {
try {
this.message.addRecipient(RecipientType.TO, new InternetAddress(to));
} catch (MessagingException exception) {
log.error(exception);
}
}
}
public String[] getTo() {
return getRecipients(RecipientType.TO);
}
/**
* Sets the mail's carbon copy receiver(s).
*
* @param cc the mail's cc receiver(s)
*/
public void setCc(String[] cc) {
if (cc != null) {
try {
for (int i = 0; i < cc.length; i++) {
this.message.addRecipient(RecipientType.CC, new InternetAddress(cc[i]));
}
} catch (MessagingException exception) {
log.error(exception);
}
}
}
/**
* Adds a carbon copy receiver.
*
* @param cc a cc receiver
*/
public void addCc(String cc) {
if (cc != null) {
try {
this.message.addRecipient(RecipientType.CC, new InternetAddress(cc));
} catch (MessagingException exception) {
log.error(exception);
}
}
}
/**
* Returns all carbon copy receivers.
*
* @return the carbon copy receivers
*/
public String[] getCc() {
return getRecipients(RecipientType.CC);
}
/**
* Sets the mail's multiple blind carbon copy receiver(s).
*
* @param bcc the mail's bcc receiver(s)
*/
public void setBcc(String[] bcc) {
if (bcc != null) {
try {
for (int i = 0; i < bcc.length; i++) {
this.message.addRecipient(RecipientType.BCC, new InternetAddress(bcc[i]));
}
} catch (MessagingException exception) {
log.error(exception);
}
}
}
/**
* Adds a blind carbon copy receiver.
*
* @param bcc a bcc receiver
*/
public void addBcc(String bcc) {
if (bcc != null) {
try {
this.message.addRecipient(RecipientType.BCC, new InternetAddress(bcc));
} catch (MessagingException exception) {
log.error(exception);
}
}
}
/**
* Returns all blind carbon copy receiver(s).
*
* @return the blind carbon copy receivers
*/
public String[] getBcc() {
return getRecipients(RecipientType.BCC);
}
/**
* Sets the mail's subject.
*
* @param subject the mail's subject
*/
public void setSubject(String subject) {
try {
this.message.setSubject(subject, this.encoding);
} catch (MessagingException exception) {
log.error(exception);
}
}
/**
* Returns the mail's subject.
*
* @return the mail's subject
*/
public String getSubject() {
String result = "";
try {
result = this.message.getSubject();
} catch (MessagingException exception) {
log.error(exception);
}
return result;
}
/**
* Sets the mail's content. This may be plain text or HTML.
* If it is HTML, a plain text alternative (in case the receiver's client won't display HTML)
* may be specified when sending the mail.
*
* @see #sendHtmlMail(String)
*
* @param bodyText the mail content to set
*/
public void setBody(String bodyText) {
this.messageText = bodyText;
}
public void appendBody(String bodyText) {
this.messageText = this.messageText + bodyText;
}
/**
* Returns a boolean indicating whether this message is valid and ready for being sent.
*
* @return a boolean indicating whether the message is valid
*/
public boolean isMailSendable() {
try {
Address[] fromArray = this.message.getFrom();
return (this.message.getSubject() != null &&
getRecipients(RecipientType.TO).length > 0 &&
fromArray != null &&
fromArray.length > 0 &&
fromArray[0] != null &&
fromArray[0].toString().length() > 0);
} catch (MessagingException exception) {
log.error(exception);
return false;
}
}
/**
* Creates an attachment by reading the specified {@code inputStream},
* creating a temporary file for the retreived data, and attaching this file.
*
* TODO Create a certain temp file directory for this operation, so that we securely may clean up this directory whenever we want to!
* TODO synchronization?!?
*
* @param inputStream the {@link InputStream} to get the attachment from
* @param fileName the attachment name to use
* @param mimeType the mimeType of the data
*/
public void addAttachmentFromInputStream(InputStream inputStream, String fileName, String mimeType) {
FileOutputStream fileOutputStream = null;
try {
String oldFileName = fileName;
String suffix = null;
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex >= 0) {
suffix = fileName.substring(dotIndex);
fileName = fileName.substring(0, dotIndex);
}
// File.createTempFile() throws IllegalArgumentException if tempFileName does not contain at least 3 characters
while (fileName.length() < 3) {
fileName = "0" + fileName;
}
File tempFile = File.createTempFile(fileName, suffix);
// temp file will be deleted when VM exits... won't be enough on a server!
tempFile.deleteOnExit();
this.tempFileNameMappings.put(tempFile.getAbsolutePath(), oldFileName);
fileOutputStream = new FileOutputStream(tempFile);
byte[] buffer = new byte[512];
for (int length = 0; (length = inputStream.read(buffer)) != -1; ) {
fileOutputStream.write(buffer, 0, length);
}
this.addAttachmentFromFile(tempFile.getAbsolutePath());
} catch (IOException exception) {
log.error("Error creating attachment " + fileName + " from inputStream", exception);
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
} catch (IOException exception) {
// ignored
}
}
}
/**
* Creates an attachment containing the specified file.
*
* @param fileNameWithPath the absolute file name
*/
public void addAttachmentFromFile(String fileNameWithPath) {
try {
File file = new File(fileNameWithPath);
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.attachFile(file);
if (this.tempFileNameMappings.containsKey(fileNameWithPath)) {
bodyPart.setFileName(this.tempFileNameMappings.get(fileNameWithPath));
}
this.attachments.add(bodyPart);
} catch (IOException exception) {
log.error("Error opening file " + fileNameWithPath, exception);
} catch (MessagingException exception) {
log.error("Error creating attachment comprising file " + fileNameWithPath);
}
}
/**
* Creates an attachment by opening a connection to the specified URL,
* creating a temporary file for the retreived data and attaching this file.
*
* TODO Check that the temp file is properly deleted after attaching it!
* TODO Create a certain temp file directory for this operation, so that we securely may clean up this directory whenever we want to!
* TODO synchronization?!?
*
* @param httpUrl the URL to get the attachment from
*/
public void addAttachmentFromUrl(String httpUrl) {
try {
URL url = new URL(httpUrl);
String fileName = url.getPath();
int slashIndex = fileName.lastIndexOf("/");
if (slashIndex >= 0) {
fileName = fileName.substring(slashIndex + 1);
}
String oldFileName = fileName;
String suffix = null;
int dotIndex = fileName.lastIndexOf('.');
if (dotIndex >= 0) {
suffix = fileName.substring(dotIndex);
fileName = fileName.substring(0, dotIndex);
}
// File.createTempFile() throws IllegalArgumentException if tempFileName does not contain at least 3 characters
while (fileName.length() < 3) {
fileName = "0" + fileName;
}
File tempFile = File.createTempFile(fileName, suffix);
// temp file will be deleted when VM exits... won't be enough on a server and does not work on win32 anyway!
tempFile.deleteOnExit();
this.tempFileNameMappings.put(tempFile.getAbsolutePath(), oldFileName);
InputStream inputStream = url.openStream();
FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
byte[] buffer = new byte[512];
for (int length = 0; (length = inputStream.read(buffer)) != -1; ) {
fileOutputStream.write(buffer, 0, length);
}
fileOutputStream.close();
inputStream.close();
this.addAttachmentFromFile(tempFile.getAbsolutePath());
} catch (MalformedURLException exception) {
log.error("The URL is invalid: " + httpUrl, exception);
} catch (IOException exception) {
log.error("Error opening the URL " + httpUrl, exception);
}
}
public boolean sendPlaintextMail() {
try {
if (this.attachments.size() > 0) {
MimeMultipart multiPart = new MimeMultipart("mixed");
BodyPart plainTextBodyPart = new MimeBodyPart();
plainTextBodyPart.setText(this.messageText);
multiPart.addBodyPart(plainTextBodyPart);
for (int i = 0; i < this.attachments.size(); i++) {
multiPart.addBodyPart(this.attachments.get(i));
}
this.message.setContent(multiPart);
} else {
this.message.setText(this.messageText, this.encoding, "plain");
}
this.message.saveChanges();
doSend();
} catch (MessagingException exception) {
log.error("Error sending plain text mail", exception);
return false;
}
return true;
}
public boolean sendHtmlMail(String alternativePlaintextBody) {
try {
if (this.attachments.size() > 0) {
MimeMultipart mainMultiPart = new MimeMultipart("mixed");
if (alternativePlaintextBody != null) {
MimeMultipart alternativeMultiPart = new MimeMultipart("alternative");
MimeBodyPart plainTextBodyPart = new MimeBodyPart();
MimeBodyPart htmlBodyPart = new MimeBodyPart();
plainTextBodyPart.setText(alternativePlaintextBody, this.encoding, "plain");
htmlBodyPart.setText(this.messageText, this.encoding, "html");
alternativeMultiPart.addBodyPart(plainTextBodyPart);
alternativeMultiPart.addBodyPart(htmlBodyPart);
MimeBodyPart containerBodyPart = new MimeBodyPart();
containerBodyPart.setContent(alternativeMultiPart);
mainMultiPart.addBodyPart(containerBodyPart);
} else { // without plain text alternative
MimeBodyPart htmlBodyPart = new MimeBodyPart();
htmlBodyPart.setText(this.messageText, this.encoding, "html");
mainMultiPart.addBodyPart(htmlBodyPart);
}
for (int i = 0; i < this.attachments.size(); i++) {
mainMultiPart.addBodyPart(this.attachments.get(i));
}
this.message.setContent(mainMultiPart);
}
else { // no attachments
if (alternativePlaintextBody != null) {
MimeMultipart mainMultipart = new MimeMultipart("alternative");
MimeBodyPart plainTextBodyPart = new MimeBodyPart();
MimeBodyPart htmlBodyPart = new MimeBodyPart();
plainTextBodyPart.setText(alternativePlaintextBody, this.encoding, "plain");
htmlBodyPart.setText(this.messageText, this.encoding, "html");
mainMultipart.addBodyPart(plainTextBodyPart);
mainMultipart.addBodyPart(htmlBodyPart);
this.message.setContent(mainMultipart);
}
else { // no alternative plain text neither -> no MimeMessage!
this.message.setContent(this.messageText, "text/html");
}
}
this.message.saveChanges();
doSend();
} catch (MessagingException exception) {
log.error("Error sending HTML mail", exception);
return false;
}
return true;
}
/**
* Allows adding display names for attachments from outside this class.
*
* @param absoluteFileName the attachment's absolute file name
* @param displayName the name for the attachment
*/
public void addNameToFileNameMappings(String absoluteFileName, String displayName) {
this.tempFileNameMappings.put(absoluteFileName, displayName);
}
/**
* Returns all recipients for the specified {@code RecipientType}.
*
* @param type the {@link RecipientType}
* @return a string array containing all recipients for the specified type
*/
private String[] getRecipients(RecipientType type) {
String[] result;
try {
Address[] addresses = this.message.getRecipients(type);
result = new String[addresses.length];
for (int i = 0; i < addresses.length; i++) {
result[i] = addresses[i].toString();
}
} catch (MessagingException exception) {
log.error(exception);
result = new String[0];
}
return result;
}
/**
* Sends the message and cleans up all temp files that were created for the attachments.
*
* @throws MessagingException
*/
private void doSend() throws MessagingException {
this.message.setSentDate(new Date());
Transport.send(this.message);
if (this.attachments.size() > 0) {
Enumeration<String> enumeration = this.tempFileNameMappings.keys();
while (enumeration.hasMoreElements()) {
File file = new File(enumeration.nextElement());
if (file.exists()) {
if (!file.delete()) {
log.error("Temp file " + file.getAbsolutePath() + " could not be deleted!");
}
}
}
this.attachments.clear();
this.tempFileNameMappings.clear();
}
}
public void clearTempFiles() {
if (this.attachments.size() > 0) {
Enumeration<String> enumeration = this.tempFileNameMappings.keys();
while (enumeration.hasMoreElements()) {
File file = new File(enumeration.nextElement());
if (file.exists()) {
if (!file.delete()) {
log.error("Temp file " + file.getAbsolutePath() + " could not be deleted!");
}
}
}
this.attachments.clear();
this.tempFileNameMappings.clear();
}
}
}