Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / mail / stdimpl / GMTransport.java
blobd3ace67208dbf7dac1529f3990335ddf2a3362c5
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;
15 import java.util.Set;
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;
33 /**
34 * Implementation of the 'Google Message Transport' which really just
35 * connects to the exposed MailService and uses it to deliver a message.
36 * <p>
37 * The special destination address "admins" results in a delivery
38 * of the message to the owners of the application.
39 * <p>
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);
56 /** {@inheritDoc} */
57 @Override
58 protected boolean protocolConnect(String host, int port,
59 String user, String password) {
60 return true;
63 /** {@inheritDoc} */
64 @Override
65 public void sendMessage(Message message, Address[] addresses)
66 throws MessagingException {
67 MailService service = MailServiceFactory.getMailService();
68 MailService.Message msg = new MailService.Message();
70 String sender = null;
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);
83 try {
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())) {
93 toAdmins = true;
98 if (!toAdmins) {
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) {
125 try {
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")) {
132 try {
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/*")) {
139 try {
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/*")) {
146 Multipart mp;
147 try {
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();
157 } else {
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) {
176 try {
177 msg.setTextBody(inputStreamToString((InputStream) textObject, textType));
178 } catch (IOException e) {
179 throw new MessagingException("Stringifying text body failed", e);
181 } else {
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) {
191 try {
192 msg.setHtmlBody(inputStreamToString((InputStream) htmlObject, htmlType));
193 } catch (IOException e) {
194 throw new MessagingException("Stringifying html body failed", e);
196 } else {
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();
207 byte[] data;
208 try {
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();
214 } else {
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);
227 try {
228 if (toAdmins) {
229 service.sendToAdmins(msg);
230 } else {
231 service.send(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);
256 return false;
260 * Converts an array of addresses into a collection of strings representing
261 * those addresses
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) {
269 return null;
271 ArrayList<String> ourAddrs = new ArrayList<String>(targetAddrs.length);
272 for (Address addr : targetAddrs) {
273 String email = addr.toString();
274 if (allAddrs.contains(email)) {
275 ourAddrs.add(email);
278 return ourAddrs;
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];
295 break;
298 if (charset != null) {
299 return new String(inputStreamToBytes(in), charset);
300 } else {
301 return new String(inputStreamToBytes(in));
306 * Gets all the available data in an InputStream and returns it as a byte
307 * array.
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);
315 return bytes;