1 // Copyright 2008 Google Inc. All rights reserved.
3 package com
.google
.appengine
.api
.mail
.stdimpl
;
5 import com
.google
.appengine
.api
.mail
.MailService
;
6 import com
.google
.appengine
.api
.mail
.MailServiceFactory
;
8 import com
.google
.common
.base
.Joiner
;
9 import java
.io
.IOException
;
10 import java
.io
.InputStream
;
11 import java
.util
.ArrayList
;
12 import java
.util
.Collection
;
13 import java
.util
.Enumeration
;
14 import java
.util
.List
;
16 import java
.util
.HashSet
;
18 import javax
.mail
.Address
;
19 import javax
.mail
.BodyPart
;
20 import javax
.mail
.Header
;
21 import javax
.mail
.Message
;
22 import javax
.mail
.MessagingException
;
23 import javax
.mail
.Multipart
;
24 import javax
.mail
.SendFailedException
;
25 import javax
.mail
.Session
;
26 import javax
.mail
.Transport
;
27 import javax
.mail
.URLName
;
28 import javax
.mail
.event
.TransportEvent
;
29 import javax
.mail
.internet
.MimeMessage
;
30 import javax
.mail
.internet
.MimeMessage
.RecipientType
;
31 import javax
.mail
.internet
.MimeMultipart
;
34 * Implementation of the 'Google Message Transport' which really just
35 * connects to the exposed MailService and uses it to deliver a message.
37 * The special destination address "admins" results in a delivery
38 * of the message to the owners of the application.
40 * Note that most RFC822 headers are silently ignored.
44 public class GMTransport
extends Transport
{
46 private static final String ADMINS_ADDRESS
= "admins";
48 private static final String
[] HEADERS_WHITELIST
= new String
[] {
49 "Auto-Submitted", "In-Reply-To", "List-Id", "List-Unsubscribe",
50 "On-Behalf-Of", "References", "Resent-Date", "Resent-From", "Resent-To"};
52 public GMTransport(Session session
, URLName urlName
) {
53 super(session
, urlName
);
58 protected boolean protocolConnect(String host
, int port
,
59 String user
, String password
) {
65 public void sendMessage(Message message
, Address
[] addresses
)
66 throws MessagingException
{
67 MailService service
= MailServiceFactory
.getMailService();
68 MailService
.Message msg
= new MailService
.Message();
71 if (message
instanceof MimeMessage
) {
72 Address senderAddr
= ((MimeMessage
) message
).getSender();
73 if (senderAddr
!= null) {
74 sender
= senderAddr
.toString();
77 if (sender
== null && message
.getFrom() != null
78 && message
.getFrom().length
> 0) {
79 sender
= message
.getFrom()[0].toString();
81 msg
.setSender(sender
);
84 msg
.setReplyTo(Joiner
.on(", ").useForNull("null").join(message
.getReplyTo()));
85 } catch (NullPointerException e
) {
88 boolean toAdmins
= false;
89 Address
[] allRecipients
= message
.getAllRecipients();
90 if (allRecipients
!= null) {
91 for (Address addr
: allRecipients
) {
92 if (ADMINS_ADDRESS
.equals(addr
.toString())) {
99 Set
<String
> allAddresses
= new HashSet
<String
>();
100 for (Address addr
: addresses
) {
101 allAddresses
.add(addr
.toString());
103 msg
.setTo(convertAddressFields(message
.getRecipients(RecipientType
.TO
), allAddresses
));
104 msg
.setCc(convertAddressFields(message
.getRecipients(RecipientType
.CC
), allAddresses
));
105 msg
.setBcc(convertAddressFields(message
.getRecipients(RecipientType
.BCC
), allAddresses
));
108 msg
.setSubject(message
.getSubject());
110 Object textObject
= null;
111 Object htmlObject
= null;
112 String textType
= null;
113 String htmlType
= null;
114 Multipart otherMessageParts
= null;
116 List
<MailService
.Header
> headers
= new ArrayList
<MailService
.Header
>();
117 Enumeration originalHeaders
= message
.getMatchingHeaders(HEADERS_WHITELIST
);
118 while (originalHeaders
.hasMoreElements()) {
119 Header header
= (Header
) originalHeaders
.nextElement();
120 headers
.add(new MailService
.Header(header
.getName(), header
.getValue()));
122 msg
.setHeaders(headers
);
124 if (message
.getContentType() == null) {
126 textObject
= message
.getContent();
127 textType
= message
.getContentType();
128 } catch (IOException e
) {
129 throw new MessagingException("Getting typeless content failed", e
);
131 } else if (message
.isMimeType("text/html")) {
133 htmlObject
= message
.getContent();
134 htmlType
= message
.getContentType();
135 } catch (IOException e
) {
136 throw new MessagingException("Getting html content failed", e
);
138 } else if (message
.isMimeType("text/*")) {
140 textObject
= message
.getContent();
141 textType
= message
.getContentType();
142 } catch (IOException e
) {
143 throw new MessagingException("Getting text/* content failed", e
);
145 } else if (message
.isMimeType("multipart/*")) {
148 mp
= (Multipart
) message
.getContent();
149 for (int i
= 0; i
< mp
.getCount(); i
++) {
150 BodyPart bp
= mp
.getBodyPart(i
);
151 if (bp
.isMimeType("text/plain") && textObject
== null) {
152 textObject
= bp
.getContent();
153 textType
= bp
.getContentType();
154 } else if (bp
.isMimeType("text/html") && htmlObject
== null) {
155 htmlObject
= bp
.getContent();
156 htmlType
= bp
.getContentType();
158 if (otherMessageParts
== null) {
159 String type
= mp
.getContentType();
160 assert (type
.startsWith("multipart/"));
161 otherMessageParts
= new MimeMultipart(
162 type
.substring("multipart/".length()));
164 otherMessageParts
.addBodyPart(bp
);
167 } catch (IOException e
) {
168 throw new MessagingException("Getting multipart content failed", e
);
172 if (textObject
!= null) {
173 if (textObject
instanceof String
) {
174 msg
.setTextBody((String
) textObject
);
175 } else if (textObject
instanceof InputStream
) {
177 msg
.setTextBody(inputStreamToString((InputStream
) textObject
, textType
));
178 } catch (IOException e
) {
179 throw new MessagingException("Stringifying text body failed", e
);
182 throw new MessagingException("Converting text body failed");
186 if (htmlObject
!= null) {
187 if (htmlObject
instanceof String
) {
189 msg
.setHtmlBody((String
) htmlObject
);
190 } else if (htmlObject
instanceof InputStream
) {
192 msg
.setHtmlBody(inputStreamToString((InputStream
) htmlObject
, htmlType
));
193 } catch (IOException e
) {
194 throw new MessagingException("Stringifying html body failed", e
);
197 throw new MessagingException("Converting html body failed");
201 if (otherMessageParts
!= null) {
202 ArrayList
<MailService
.Attachment
> attachments
=
203 new ArrayList
<MailService
.Attachment
>(otherMessageParts
.getCount());
204 for (int i
= 0; i
< otherMessageParts
.getCount(); i
++) {
205 BodyPart bp
= otherMessageParts
.getBodyPart(i
);
206 String name
= bp
.getFileName();
209 Object o
= bp
.getContent();
210 if (o
instanceof InputStream
) {
211 data
= inputStreamToBytes((InputStream
) o
);
212 } else if (o
instanceof String
) {
213 data
= ((String
) o
).getBytes();
215 throw new MessagingException("Converting attachment data failed");
217 } catch (IOException e
) {
218 throw new MessagingException("Extracting attachment data failed", e
);
220 MailService
.Attachment attachment
=
221 new MailService
.Attachment(name
, data
);
222 attachments
.add(attachment
);
224 msg
.setAttachments(attachments
);
229 service
.sendToAdmins(msg
);
233 } catch (IOException e
) {
234 notifyTransportListeners(
235 TransportEvent
.MESSAGE_NOT_DELIVERED
, new Address
[0], addresses
,
236 new Address
[0], message
);
237 throw new SendFailedException("MailService IO failed", e
);
238 } catch (IllegalArgumentException e
) {
239 throw new MessagingException("Illegal Arguments", e
);
242 notifyTransportListeners(
243 TransportEvent
.MESSAGE_DELIVERED
, addresses
, new Address
[0],
244 new Address
[0], message
);
247 public int hashCode() {
248 return session
.hashCode() * 13 + url
.hashCode();
251 public boolean equals(Object obj
) {
252 if (obj
instanceof GMTransport
) {
253 GMTransport transport
= (GMTransport
) obj
;
254 return session
.equals(transport
.session
) && url
.equals(transport
.url
);
260 * Converts an array of addresses into a collection of strings representing
262 * @param targetAddrs addresses to be converted
263 * @param allAddrs all addresses for this transport
264 * @return A collection of strings representing the intersection
265 * between targetAddrs and allAddrs.
267 private Collection
<String
> convertAddressFields(Address
[] targetAddrs
, Set
<String
> allAddrs
) {
268 if (targetAddrs
== null || targetAddrs
.length
== 0) {
271 ArrayList
<String
> ourAddrs
= new ArrayList
<String
>(targetAddrs
.length
);
272 for (Address addr
: targetAddrs
) {
273 String email
= addr
.toString();
274 if (allAddrs
.contains(email
)) {
282 * Gets all the available data in an InputStream and returns it as a String
283 * using the character set specified in the type parameter.
284 * @param in The input stream to be read.
285 * @param type The encoding type of the stream.
286 * @return A String containing the data.
287 * @throws IOException If there is a problem with the input stream.
289 private String
inputStreamToString(InputStream in
, String type
) throws IOException
{
290 String charset
= null;
291 String
[] args
= type
.split(";");
292 for (String arg
: args
) {
293 if (arg
.trim().startsWith("charset=")) {
294 charset
= arg
.split("=")[1];
298 if (charset
!= null) {
299 return new String(inputStreamToBytes(in
), charset
);
301 return new String(inputStreamToBytes(in
));
306 * Gets all the available data in an InputStream and returns it as a byte
308 * @param in The input stream to be read.
309 * @return A byte array containing the data.
310 * @throws IOException If there is a problem with the input stream.
312 private byte[] inputStreamToBytes(InputStream in
) throws IOException
{
313 byte[] bytes
= new byte[in
.available()];
314 int count
= in
.read(bytes
);