#

In memory mail creation

Wenn man mit der Mail API von Java arbeitet und Multipart Mails on the fly im Speicher erstellen möchte,
stö�t man gelegentlich auf Probleme. Dieser Beitrag soll die Erstellung einer Multipart Mail mit binären Anhängen an einem einfachen Beispiel erklären. Hilfreich, wenn man z. B. Daten aus einer Datenbank oder einem HTML Formular kriegt, und diese ohne ein Caching auf der Platte an eine Mail anfügen möchte.

Im ersten Schritt werden (auf eine beliebige Weise) die anzuhängenden Daten in einen String, einen byte-Array InputStream oder Reader gekapselt.
Im Falle eines Blob aus einer Datenbank könnte dies wie folgt aussehen:

Blob blob = rs.getBlob("binarydata");
InputStream in = blob.getBinaryStream();
String filename = rs.getString("filename");

Für eine Multipart MIME Mail wird zusätzlich der MIME-Typ der Daten benötigt, damit der Mailclient bei bedarf die Daten richtig Darstellen oder mit dem richtigen Programm öffnen kann.
Die folgende kleine Klasse übernimmt das Mapping von einem Dateinamen in den entsprechenden MIME Typen und ist einfach erweiterbar:

import java.util.HashMap;
import java.util.Map;
 
public class FileExtensionToMIMETypeMapper {
 
private FileExtensionToMIMETypeMapper() {}
 
private static final Map extensionToMimeMapping =
new HashMap();
public static final String DEFAULT_MIME_TYPE = 
"application/octet-stream";
static{
extensionToMimeMapping.put(null, DEFAULT_MIME_TYPE);
extensionToMimeMapping.put("ai", "application/postscript");
extensionToMimeMapping.put("doc", "application/msword");
extensionToMimeMapping.put("dot", "application/msword");
extensionToMimeMapping.put("eps", "application/postscript");
extensionToMimeMapping.put("gif", "image/gif");
extensionToMimeMapping.put("htm", "text/html");
extensionToMimeMapping.put("html", "text/html");
extensionToMimeMapping.put("jpe", "image/jpeg");
extensionToMimeMapping.put("jpeg", "image/jpeg");
extensionToMimeMapping.put("jpg", "image/jpeg");
extensionToMimeMapping.put("pdf", "application/pdf");
extensionToMimeMapping.put("ps", "application/postscript");
extensionToMimeMapping.put("rtf", "application/rtf");
extensionToMimeMapping.put("tif", "image/tiff");
extensionToMimeMapping.put("tiff", "image/tiff");
extensionToMimeMapping.put("txt", "text/plain");
}
 
public static String getMIMETypeForFileName(String filename){
// get fileextension
String extension = null;
if (filename==null) return DEFAULT_MIME_TYPE;
 
String filenameLowercase = filename.toLowerCase();
int lastindex = filenameLowercase.lastIndexOf('.');
int filenamelen = filenameLowercase.length();
if (lastindex>-1 && lastindex+1<filenamelen){
extension = filenameLowercase.
substring(lastindex+1, filenamelen);
}
return getMIMETypeForFileExtension(extension);
}
 
public static String getMIMETypeForFileExtension(String extension){
if (extension==null) return DEFAULT_MIME_TYPE;
String extensionLowercase = extension.toLowerCase();
String mime = (String) extensionToMimeMapping
.get(extensionLowercase);
if (mime==null) return DEFAULT_MIME_TYPE;
else return mime;
}
 
public static void main(String argv[]){
String [] testnames = new String[]
{"test.txt", "test.test.gif", "test.tx", "test", "test."};
for (int i=0; i<testnames.length;i++)
System.out.println(testnames[i]+": "
+getMIMETypeForFileName(testnames[i]));
}
}

Jedes Attachment einer Mail muss in ein Objekt des Typs MimeBodyPart gekapsellt werden. Hierfür verfügt der MimeBodyPart zwei wichtige Methoden: setFileName(String filename) und setDataHandler(DataHandler datahandler).
Beim DataHandler handelt es sich um ein Interface, mit dem der MimeBodyPart an den MIME Typen und den binären Inhalt herankommt. Der DataHandler wiederum benutzt ein javax.activation.DataSource Objekt, welches die eigentlichen Daten enthält.
Diverse vorhandene DataSource Versionen ermöglichen die �bergabe verschiedener Datenquellen, wie z. B. FileDataSource mit einer Datei als Datenquelle. Um das ganze jedoch im Speicher zu bewältigen, benötigen wir eine eigene Implementierung von DataSource. Die folgende DataSource wurde aus einem von Sun zur Verfügung gestellten Beispiel basierend erweitert.

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
 
import javax.activation.DataSource;
 
/**
 * A simple DataSource for demonstration purposes.
 * This class implements a DataSource from:
 * an InputStream
 *a byte array
 * a String
 *
 * @author John Mani
 * @author Bill Shannon
 * @author Max Spivak
 */
public class ByteArrayDataSource implements DataSource {
private byte[] data;// data
private String type;// content-type
 
/* Create a DataSource from an input stream */
public ByteArrayDataSource(InputStream is, String type) {
this.type = type;
try { 
ByteArrayOutputStream os = new 
ByteArrayOutputStream();
int ch;
 
while ((ch = is.read()) != -1)
// XXX - must be made more efficient by
// doing buffered reads,
// rather than one byte reads
os.write(ch);
data = os.toByteArray();
 
} 
catch (IOException ioex) { }
}
 
/* Create a DataSource from a reder */
public ByteArrayDataSource(Reader rd, String type) {
this.type = type;
StringBuffer sb = new StringBuffer();
BufferedReader bin = null;
try{
bin = new BufferedReader(rd);
String line;
while((line=bin.readLine())!=null){
sb.append(line);
}
this.data = sb.toString().getBytes(type);
}
catch(IOException e){}
finally{
try {
bin.close();
}
catch (IOException e) {}
}
}
 
/* Create a DataSource from a byte array */
public ByteArrayDataSource(byte[] data, String type) {
this.data = data;
this.type = type;
}
 
/* Create a DataSource from a String */
public ByteArrayDataSource(String data, String type) {
try {
this.data = data.getBytes(type);
} catch (UnsupportedEncodingException uex) { }
this.type = type;
}
 
/**
 * Return an InputStream for the data.
 * Note - a new stream must be returned each time.
 */
public InputStream getInputStream()
throws IOException {
if (data == null)
throw new IOException("no data");
return new ByteArrayInputStream(data);
}
 
public OutputStream getOutputStream()
throws IOException {
throw new IOException("cannot do this");
}
 
public String getContentType() {
return type;
}
 
public String getName() {
return "dummy";
}
}

Nun kann die MimeBodyPart erstellt werden:

MimeBodyPart mbp = new MimeBodyPart();
String mimetype = FileExtensionToMIMETypeMapper
.getMIMETypeForFileName(filename);
mbp.setFileName(filename);
mbp.setDataHandler(new DataHandler(
new ByteArrayDataSource(in, mimetype)));

Mehrere oder auch nur ein einzelner MimeBodyPart müssen einem Multipart Objekt übergeben werden:

Multipart mp = new MimeMultipart();
mp.addBodyPart(<b>mbp</b>);

Nun kann der Rest der Email konfiguriert und diese abgeschickt werden:

Properties mailProperties = new Properties();
mailProperties.put("mail.smtp.host", "smtp.anywhere.de"));
Session mailSession = Session.getDefaultInstance(mailProperties, null);
Message msg = new MimeMessage(mailSession);
msg.setFrom(new InternetAddress("me@myhost.com"));
msg.addRecipient(Message.RecipientType.TO, 
new InternetAddress("other@other.com"));
msg.setSubject("Hello!"); 
msg.setSentDate(new Date());
msg.setContent(<b>mp</b>);
Transport.send(msg);
Tags:, ,

Leave a Reply »»

Note: All comments are manually approved to avoid spam. So if your comment doesn't appear immediately, that's ok. Have patience, it can take some days until I have the time to approve my comments.