forked from aws/aws-sdk-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAWSJavaMailTransport.java
More file actions
358 lines (318 loc) · 13.2 KB
/
AWSJavaMailTransport.java
File metadata and controls
358 lines (318 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*
* Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazonaws.services.simpleemail;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.event.TransportEvent;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.simpleemail.model.RawMessage;
import com.amazonaws.services.simpleemail.model.SendRawEmailRequest;
import com.amazonaws.util.VersionInfoUtils;
/**
* A transport implementation using Amazon Web Service's E-mail Service. For
* JavaMail purposes this transport implementation uses the "aws" protocol. In
* order to send messages through the E-mail Service your AWS Credentials
* (http://aws.amazon.com/security-credentials) need to be either in the
* JavaMail Session's Properties (mail.aws.user and mail.aws.password), passed
* into the connect() method, or set in the Session's setPasswordAuthentication
* method. Parameters passed into the connect method as well as
* PasswordAuthentication information supersedes the properties field for a
* particular session. When connecting your AWS Access Key is your username and
* your AWS Secret Key is your password.
* <p>
* This transport implementation only accepts MIME encoded messages (see
* MimeMessage class) and RFC822 E-mail addresses (see InternetAddress class).
*/
public class AWSJavaMailTransport extends Transport {
public static final String AWS_EMAIL_SERVICE_ENDPOINT_PROPERTY = "mail.aws.host";
public static final String AWS_SECRET_KEY_PROPERTY = "mail.aws.password";
public static final String AWS_ACCESS_KEY_PROPERTY = "mail.aws.user";
private AmazonSimpleEmailServiceClient emailService;
private final String accessKey;
private final String secretKey;
private final String httpsEndpoint;
public AWSJavaMailTransport(Session session, URLName urlname) {
super(session, urlname);
this.accessKey = session.getProperty(AWS_ACCESS_KEY_PROPERTY);
this.secretKey = session.getProperty(AWS_SECRET_KEY_PROPERTY);
this.httpsEndpoint = session.getProperty(AWS_EMAIL_SERVICE_ENDPOINT_PROPERTY);
}
/**
* Sends a MIME message through Amazon's E-mail Service with the specified
* recipients. Addresses that are passed into this method are merged with
* the ones already embedded in the message (duplicates are removed).
*
* @param msg
* A Mime type e-mail message to be sent
* @param addresses
* Additional e-mail addresses (RFC-822) to be included in the
* message
*/
@Override
public void sendMessage(Message msg, Address[] addresses)
throws MessagingException, SendFailedException {
checkConnection();
checkMessage(msg);
checkAddresses(msg, addresses);
collateRecipients(msg, addresses);
SendRawEmailRequest req = prepareEmail(msg);
sendEmail(msg, req);
}
/**
* Asserts a valid connection to the email service.
*/
private void checkConnection() {
if (emailService == null || !super.isConnected()) {
throw new IllegalStateException("Not connected");
}
}
/**
* Checks that the message can be sent using AWS Simple E-mail Service.
*/
private void checkMessage(Message msg) throws MessagingException {
if (msg == null) {
throw new MessagingException("Message is null");
}
if (!(msg instanceof MimeMessage)) {
throw new MessagingException(
"AWS Mail Service can only send MimeMessages");
}
}
/**
* Checks to ensure at least one recipient is present (either in the Message
* or Address[]) and all addresses that are passed in using the Address
* array are of type InternetAddress.
*/
private void checkAddresses(Message m, Address[] addresses)
throws MessagingException, SendFailedException {
if ( isNullOrEmpty((Object[]) addresses)
&& isNullOrEmpty((Object[]) m.getRecipients(Message.RecipientType.TO))
&& isNullOrEmpty((Object[]) m.getRecipients(Message.RecipientType.CC))
&& isNullOrEmpty((Object[]) m.getRecipients(Message.RecipientType.BCC)) ) {
throw new SendFailedException("No recipient addresses");
}
// Make sure all addresses are internet addresses
Set<Address> invalid = new HashSet<Address>();
for ( Address[] recipients : new Address[][] {
m.getRecipients(Message.RecipientType.TO),
m.getRecipients(Message.RecipientType.CC),
m.getRecipients(Message.RecipientType.BCC),
addresses } ) {
if ( !isNullOrEmpty(recipients) ) {
for ( Address a : recipients ) {
if ( !(a instanceof InternetAddress) ) {
invalid.add(a);
}
}
}
}
if ( !invalid.isEmpty() ) {
Address[] sent = new Address[0];
Address[] unsent = new Address[0];
super.notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent,
invalid.toArray(new Address[invalid.size()]), m);
throw new SendFailedException("AWS Mail Service can only send to InternetAddresses");
}
}
/**
* Collates any addresses into the message object. All addresses in the Address array
* become of type TO unless they already exist in the Message header. If
* they are in the Message header they will stay of the same type. Any
* duplicate addresses are removed. Type BCC and then CC takes precedence
* over TO when duplicates exist. If any address is invalid an exception is
* thrown.
*/
private void collateRecipients(Message m, Address[] addresses) throws MessagingException {
if ( !isNullOrEmpty(addresses) ) {
Hashtable<Address, Message.RecipientType> addressTable = new Hashtable<Address, Message.RecipientType>();
for ( Address a : addresses ) {
addressTable.put(a, Message.RecipientType.TO);
}
if ( !isNullOrEmpty(m.getRecipients(Message.RecipientType.TO)) ) {
for ( Address a : m.getRecipients(Message.RecipientType.TO) ) {
addressTable.put(a, Message.RecipientType.TO);
}
}
if ( !isNullOrEmpty(m.getRecipients(Message.RecipientType.CC)) ) {
for ( Address a : m.getRecipients(Message.RecipientType.CC) ) {
addressTable.put(a, Message.RecipientType.CC);
}
}
if ( !isNullOrEmpty(m.getRecipients(Message.RecipientType.BCC)) ) {
for ( Address a : m.getRecipients(Message.RecipientType.BCC) ) {
addressTable.put(a, Message.RecipientType.BCC);
}
}
// Clear the original recipients for collation
m.setRecipients(Message.RecipientType.TO, new Address[0]);
m.setRecipients(Message.RecipientType.CC, new Address[0]);
m.setRecipients(Message.RecipientType.BCC, new Address[0]);
Iterator<Address> aIter = addressTable.keySet().iterator();
while ( aIter.hasNext() ) {
Address a = aIter.next();
m.addRecipient(addressTable.get(a), a);
}
// Simple E-mail needs at least one TO address, so add one if there isn't one
if ( m.getRecipients(Message.RecipientType.TO) == null ||
m.getRecipients(Message.RecipientType.TO).length == 0 ) {
m.setRecipient(Message.RecipientType.TO, addressTable.keySet().iterator().next());
}
}
}
/**
* Prepares the email to be sent using the JavaMail service. Wraps up the
* message into a RawEmailRequest object to be processed by AWS's
* sendRawEmail().
*
* @param m
* A JavaMail message to be converted to a request
* @return A Raw Email Request for AWS E-mail Service
*/
private SendRawEmailRequest prepareEmail(Message m)
throws MessagingException {
try {
OutputStream byteOutput = new ByteArrayOutputStream();
m.writeTo(byteOutput);
SendRawEmailRequest req = new SendRawEmailRequest();
byte[] messageByteArray = ((ByteArrayOutputStream) byteOutput)
.toByteArray();
RawMessage message = new RawMessage();
message.setData(ByteBuffer.wrap(messageByteArray));
req.setRawMessage(message);
return req;
} catch (Exception e) {
Address[] sent = new Address[0];
Address[] unsent = new Address[0];
Address[] invalid = m.getAllRecipients();
super.notifyTransportListeners(
TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent,
invalid, m);
throw new MessagingException("Unable to write message: "
+ m.toString(), e);
}
}
/**
* Sends an email using AWS E-mail Service and notifies listeners
*
* @param m
* Message used to notify users
* @param req
* Raw email to be sent
*/
private void sendEmail(Message m, SendRawEmailRequest req)
throws SendFailedException, MessagingException {
Address[] sent = null;
Address[] unsent = null;
Address[] invalid = null;
try {
appendUserAgent(req, USER_AGENT);
this.emailService.sendRawEmail(req);
sent = m.getAllRecipients();
unsent = new Address[0];
invalid = new Address[0];
super.notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
sent, unsent, invalid, m);
} catch (Exception e) {
sent = new Address[0];
unsent = m.getAllRecipients();
invalid = new Address[0];
super.notifyTransportListeners(
TransportEvent.MESSAGE_NOT_DELIVERED, sent, unsent,
invalid, m);
throw new SendFailedException("Unable to send email", e, sent,
unsent, invalid);
}
}
/**
* Sets up a new AmazonSimpleEmailServiceClient. This method is typically called
* indirectly from the connect() method and should only be called on
* instantiation or to reopen after a close(). If a non-null or empty User
* and Password passed in session properties are overridden while user
* remains connected (mail.aws.user and mail.aws.password). The default
* https endpoint is specified by the mail client; however, it can be
* overridden by either passing in a value or setting mail.aws.host. Like
* the user and password, the variable that is passed in takes preference
* over the properties file.
*
* @param host
* Optional - host specifies the AWS E-mail endpoint
* @param awsAccessKey
* Optional - AWS Access Key (otherwise must specify through
* properties file)
* @param awsSecretKey
* Optional - AWS Secret key (otherwise must specify through
* properties file)
* @return Returns true if non-empty credentials are given
*/
@Override
protected boolean protocolConnect(String host, int port, String awsAccessKey,
String awsSecretKey) {
if (isConnected())
throw new IllegalStateException("Already connected");
if (isNullOrEmpty(awsAccessKey) || isNullOrEmpty(awsSecretKey)) {
if (isNullOrEmpty(accessKey) || isNullOrEmpty(secretKey)) {
// Use the no-argument constructor to fall back on:
// - Environment Variables
// - Java System Properties
// - Instance profile credentials delivered through the Amazon EC2 metadata service
this.emailService = new AmazonSimpleEmailServiceClient();
}
awsAccessKey = this.accessKey;
awsSecretKey = this.secretKey;
}
if (this.emailService == null) {
// Use the supplied credentials.
this.emailService = new AmazonSimpleEmailServiceClient(new BasicAWSCredentials(awsAccessKey, awsSecretKey));
}
if (!isNullOrEmpty(host)) {
this.emailService.setEndpoint(host);
} else if (this.httpsEndpoint != null) {
this.emailService.setEndpoint(this.httpsEndpoint);
}
super.setConnected(true);
return true;
}
@Override
public void close() throws MessagingException {
super.close();
this.emailService = null;
}
private static boolean isNullOrEmpty(String s) {
return (s == null || s.length() == 0);
}
private static boolean isNullOrEmpty(Object[] o) {
return (o == null || o.length == 0);
}
public <X extends AmazonWebServiceRequest> X appendUserAgent(X request, String userAgent) {
request.getRequestClientOptions().addClientMarker(USER_AGENT);
return request;
}
private static final String USER_AGENT = AWSJavaMailTransport.class.getName() + "/" + VersionInfoUtils.getVersion();
}