1 package com.ozacc.mail.impl;
2
3 import java.io.UnsupportedEncodingException;
4 import java.util.Date;
5 import java.util.Properties;
6
7 import javax.mail.AuthenticationFailedException;
8 import javax.mail.MessagingException;
9 import javax.mail.Session;
10 import javax.mail.Transport;
11 import javax.mail.internet.InternetAddress;
12 import javax.mail.internet.MimeMessage;
13
14 import org.apache.commons.logging.Log;
15 import org.apache.commons.logging.LogFactory;
16
17 import com.ozacc.mail.Mail;
18 import com.ozacc.mail.MailAuthenticationException;
19 import com.ozacc.mail.MailBuildException;
20 import com.ozacc.mail.MailException;
21 import com.ozacc.mail.MailSendException;
22 import com.ozacc.mail.SendMail;
23
24 /***
25 * SendMailインターフェースの実装クラス。
26 *
27 * @since 1.0
28 * @author Tomohiro Otsuka
29 * @version $Id: SendMailImpl.java,v 1.7.2.2 2005/01/22 09:03:17 otsuka Exp $
30 */
31 public class SendMailImpl implements SendMail {
32
33 private static Log log = LogFactory.getLog(SendMailImpl.class);
34
35 /*** デフォルトのプロトコル。「smtp」 */
36 public static final String DEFAULT_PROTOCOL = "smtp";
37
38 /***
39 * デフォルトのポート。「-1」<br>
40 * -1はプロトコルに応じた適切なポートを設定する特別な値。
41 * */
42 public static final int DEFAULT_PORT = -1;
43
44 /*** デフォルトのSMTPサーバ。「localhost」 */
45 public static final String DEFAULT_HOST = "localhost";
46
47 /*** ISO-2022-JP */
48 public static final String JIS_CHARSET = "ISO-2022-JP";
49
50 private static final String RETURN_PATH_KEY = "mail.smtp.from";
51
52 /*** 接続タイムアウト */
53 private static final int DEFAULT_CONNECTION_TIMEOUT = 5000;
54
55 /*** 読込タイムアウト */
56 private static final int DEFAULT_READ_TIMEOUT = 5000;
57
58 private String protocol = DEFAULT_PROTOCOL;
59
60 private String host = DEFAULT_HOST;
61
62 private int port = DEFAULT_PORT;
63
64 private String username;
65
66 private String password;
67
68 private String charset = JIS_CHARSET;
69
70 private String returnPath;
71
72 private String messageId;
73
74 private int connectionTimeout = DEFAULT_CONNECTION_TIMEOUT;
75
76 private int readTimeout = DEFAULT_READ_TIMEOUT;
77
78 /***
79 * コンストラクタ。
80 */
81 public SendMailImpl() {}
82
83 /***
84 * コンストラクタ。使用するSMTPサーバを指定します。
85 *
86 * @param host SMTPサーバのホスト名、またはIPアドレス
87 */
88 public SendMailImpl(String host) {
89 this();
90 setHost(host);
91 }
92
93 /***
94 * @see com.ozacc.mail.SendMail#send(com.ozacc.mail.Mail)
95 */
96 public void send(Mail mail) throws MailException {
97 send(new Mail[] { mail });
98 }
99
100 /***
101 * @see com.ozacc.mail.SendMail#send(com.ozacc.mail.Mail[])
102 */
103 public void send(Mail[] mails) throws MailException {
104 MimeMessageWrapper[] mmws = new MimeMessageWrapper[mails.length];
105 Session session = Session.getInstance(new Properties());
106 for (int i = 0; i < mails.length; i++) {
107 Mail mail = mails[i];
108
109
110 MimeMessage message = createMimeMessage(session);
111 MimeMessageBuilder builder = new MimeMessageBuilder(message);
112 try {
113 builder.buildMimeMessage(mail);
114 } catch (UnsupportedEncodingException e) {
115 throw new MailBuildException("サポートされていない文字コードが指定されました。", e);
116 } catch (MessagingException e) {
117 throw new MailBuildException("MimeMessageの生成に失敗しました。", e);
118 }
119
120
121 String returnPath;
122 if (mail.getReturnPath() != null) {
123 returnPath = mail.getReturnPath().getAddress();
124 } else {
125 returnPath = this.returnPath;
126 }
127
128 mmws[i] = new MimeMessageWrapper(message, returnPath, mail.getEnvelopeTo());
129 }
130 processSend(mmws);
131 }
132
133 /***
134 * @see com.ozacc.mail.SendMail#send(javax.mail.internet.MimeMessage)
135 */
136 public void send(MimeMessage message) throws MailException {
137 send(new MimeMessage[] { message });
138 }
139
140 /***
141 * @see com.ozacc.mail.SendMail#send(javax.mail.internet.MimeMessage[])
142 */
143 public void send(MimeMessage[] messages) throws MailException {
144 MimeMessageWrapper[] mmws = new MimeMessageWrapper[messages.length];
145 for (int i = 0; i < messages.length; i++) {
146 mmws[i] = new MimeMessageWrapper(messages[i], returnPath);
147 }
148 processSend(mmws);
149 }
150
151 private void processSend(MimeMessageWrapper[] mmws) throws MailException {
152
153 Properties prop = new Properties();
154
155 prop.put("mail.smtp.connectiontimeout", String.valueOf(connectionTimeout));
156 prop.put("mail.smtp.timeout", String.valueOf(readTimeout));
157 Session session = Session.getInstance(prop);
158
159 Transport transport = null;
160 try {
161
162 log.debug("SMTPサーバ[" + host + "]に接続します。");
163 transport = session.getTransport(protocol);
164 transport.connect(host, port, username, password);
165 log.debug("SMTPサーバ[" + host + "]に接続しました。");
166
167 for (int i = 0; i < mmws.length; i++) {
168 MimeMessage mimeMessage = mmws[i].getMimeMessage();
169 String returnPath = mmws[i].getReturnPath();
170
171
172 if (returnPath != null) {
173 session.getProperties().put(RETURN_PATH_KEY, returnPath);
174 log.debug("Return-Path[" + returnPath + "]を設定しました。");
175 }
176
177
178 mimeMessage.setSentDate(new Date());
179
180 mimeMessage.saveChanges();
181
182
183 log.debug("メールを送信します。");
184 if (mmws[i].hasEnvelopeTo()) {
185 log.debug("メールはenvelope-toアドレスに送信されます。");
186 transport.sendMessage(mimeMessage, mmws[i].getEnvelopeTo());
187 } else {
188 transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
189 }
190 log.debug("メールを送信しました。");
191
192
193 if (returnPath != null) {
194 session.getProperties().remove(RETURN_PATH_KEY);
195 log.debug("Return-Path設定をクリアしました。");
196 }
197 }
198 } catch (AuthenticationFailedException ex) {
199 log.error("SMTPサーバ[" + host + "]への接続認証に失敗しました。", ex);
200 throw new MailAuthenticationException(ex);
201 } catch (MessagingException ex) {
202 log.error("メールの送信に失敗しました。", ex);
203 throw new MailSendException("メールの送信に失敗しました。", ex);
204 } finally {
205 if (transport != null && transport.isConnected()) {
206 log.debug("SMTPサーバ[" + host + "]との接続を切断します。");
207 try {
208
209 transport.close();
210 } catch (MessagingException e) {
211 log.error("SMTPサーバ[" + host + "]との接続切断に失敗しました。", e);
212 throw new MailException("SMTPサーバ[" + host + "]との接続切断に失敗しました。");
213 }
214 log.debug("SMTPサーバ[" + host + "]との接続を切断しました。");
215 }
216 }
217 }
218
219 /***
220 * 新しいMimeMessageオブジェクトを生成します。<br>
221 * messageIdプロパティがセットされている場合、OMLMimeMessageのインスタンスを生成します。
222 *
223 * @return 新しいMimeMessageオブジェクト
224 */
225 private MimeMessage createMimeMessage(Session session) {
226 if (messageId == null) {
227 return new MimeMessage(session);
228 }
229 return new OMLMimeMessage(session, messageId);
230 }
231
232 /***
233 * エンコーディングに使用する文字コードを返します。
234 *
235 * @return エンコーディングに使用する文字コード
236 */
237 public String getCharset() {
238 return charset;
239 }
240
241 /***
242 * メールの件名や本文のエンコーディングに使用する文字コードを指定します。
243 * デフォルトは<code>ISO-2022-JP</code>です。
244 * <p>
245 * 日本語環境で利用する場合は通常変更する必要はありません。
246 *
247 * @param charset エンコーディングに使用する文字コード
248 */
249 public void setCharset(String charset) {
250 this.charset = charset;
251 }
252
253 /***
254 * セットされたSMTPサーバのホスト名、またはIPアドレスを返します。
255 *
256 * @return SMTPサーバのホスト名、またはIPアドレス
257 */
258 public String getHost() {
259 return host;
260 }
261
262 /***
263 * SMTPサーバのホスト名、またはIPアドレスをセットします。
264 * デフォルトは localhost です。
265 *
266 * @param host SMTPサーバのホスト名、またはIPアドレス
267 */
268 public void setHost(String host) {
269 this.host = host;
270 }
271
272 /***
273 * @return SMTPサーバ認証パスワード
274 */
275 public String getPassword() {
276 return password;
277 }
278
279 /***
280 * SMTPサーバの接続認証が必要な場合にパスワードをセットします。
281 *
282 * @param password SMTPサーバ認証パスワード
283 */
284 public void setPassword(String password) {
285 this.password = password;
286 }
287
288 /***
289 * @return SMTPサーバのポート番号
290 */
291 public int getPort() {
292 return port;
293 }
294
295 /***
296 * SMTPサーバのポート番号をセットします。
297 *
298 * @param port SMTPサーバのポート番号
299 */
300 public void setPort(int port) {
301 this.port = port;
302 }
303
304 /***
305 * @return Returns the protocol.
306 */
307 public String getProtocol() {
308 return protocol;
309 }
310
311 /***
312 * @param protocol The protocol to set.
313 */
314 public void setProtocol(String protocol) {
315 this.protocol = protocol;
316 }
317
318 /***
319 * @return Return-Pathアドレス
320 */
321 public String getReturnPath() {
322 return returnPath;
323 }
324
325 /***
326 * Return-Pathアドレスをセットします。
327 * <p>
328 * 送信するMailインスタンスに指定されたFromアドレス以外のアドレスをReturn-Pathとしたい場合に使用します。
329 * ここでセットされたReturn-Pathより、MailインスタンスにセットされたReturn-Pathが優先されます。
330 *
331 * @param returnPath Return-Pathアドレス
332 */
333 public void setReturnPath(String returnPath) {
334 this.returnPath = returnPath;
335 }
336
337 /***
338 * @return SMTPサーバ認証ユーザ名
339 */
340 public String getUsername() {
341 return username;
342 }
343
344 /***
345 * SMTPサーバの接続認証が必要な場合にユーザ名をセットします。
346 *
347 * @param username SMTPサーバ認証ユーザ名
348 */
349 public void setUsername(String username) {
350 this.username = username;
351 }
352
353 /***
354 * SMTPサーバとの接続タイムアウトをセットします。
355 * 単位はミリ秒。デフォルトは5,000ミリ秒(5秒)です。
356 * <p>
357 * -1を指定すると無限大になりますが、お薦めしません。
358 *
359 * TODO: タイムアウトになると・・・
360 *
361 * @since 1.1.4
362 * @param connectionTimeout SMTPサーバとの接続タイムアウト
363 */
364 public void setConnectionTimeout(int connectionTimeout) {
365 this.connectionTimeout = connectionTimeout;
366 }
367
368 /***
369 * SMTPサーバへの送受信時のタイムアウトをセットします。
370 * 単位はミリ秒。デフォルトは5,000ミリ秒(5秒)です。
371 * <p>
372 * -1を指定すると無限大になりますが、お薦めしません。
373 *
374 * @since 1.1.4
375 * @param readTimeout SMTPサーバへの送受信時のタイムアウト
376 */
377 public void setReadTimeout(int readTimeout) {
378 this.readTimeout = readTimeout;
379 }
380
381 /***
382 * 生成されるMimeMessageに付けられるMessage-Idヘッダのドメイン部分を指定します。<br>
383 * 指定されない場合(nullや空文字列の場合)は、JavaMailがMessage-Idヘッダを生成します。
384 * JavaMailが生成する「JavaMail.実行ユーザ名@ホスト名」のMessage-Idを避けたい場合に、このメソッドを使用します。
385 * <p>
386 * messageIdプロパティがセットされている場合、Mailから生成されるMimeMessageのMessage-Idには
387 * <code>タイムスタンプ + ランダムに生成される16桁の数値 + ここでセットされた値</code>
388 * が使用されます。
389 * <p>
390 * 生成されるMessage-Idの例。 (実際の数値部分は送信メール毎に変わります)<ul>
391 * <li>messageIdに'example.com'を指定した場合・・・1095714924963.5619528074501343@example.com</li>
392 * <li>messageIdに'@example.com'を指定した場合・・・1095714924963.5619528074501343@example.com (上と同じ)</li>
393 * <li>messageIdに'OML@example.com'を指定した場合・・・1095714924963.5619528074501343.OML@example.com</li>
394 * <li>messageIdに'.OML@example.com'を指定した場合・・・1095714924963.5619528074501343.OML@example.com (上と同じ)</li>
395 * </ul>
396 * <p>
397 * <strong>注:</strong> このMessage-Idは<code>send(Mail)</code>か<code>send(Mail[])</code>メソッドが呼びだれた時にのみ有効です。MimeMessageを直接送信する場合には適用されません。
398 *
399 * @param messageId メールに付けられるMessage-Idヘッダのドメイン部分
400 * @throws IllegalArgumentException @を複数含んだ文字列を指定した場合
401 */
402 public void setMessageId(String messageId) {
403 if (messageId == null || messageId.length() < 1) {
404 return;
405 }
406
407 String[] parts = messageId.split("@");
408 if (parts.length > 2) {
409 throw new IllegalArgumentException("messageIdプロパティに'@'を複数含むことはできません。[" + messageId
410 + "]");
411 }
412
413 this.messageId = messageId;
414 }
415
416 /***
417 * MimeMessageインスタンスと、そのメールに対応するReturn-Path、envelope-toアドレスをラップするクラス。
418 *
419 * @author Tomohiro Otsuka
420 * @version $Id: SendMailImpl.java,v 1.7.2.2 2005/01/22 09:03:17 otsuka Exp $
421 */
422 private static class MimeMessageWrapper {
423
424 private MimeMessage mimeMessage;
425
426 private String returnPath;
427
428 private InternetAddress[] envelopeTo;
429
430 public MimeMessageWrapper(MimeMessage mimeMessage, String returnPath) {
431 this.mimeMessage = mimeMessage;
432 this.returnPath = returnPath;
433 }
434
435 public MimeMessageWrapper(MimeMessage mimeMessage, String returnPath,
436 InternetAddress[] envelopeTo) {
437 this.mimeMessage = mimeMessage;
438 this.returnPath = returnPath;
439 this.envelopeTo = envelopeTo;
440 }
441
442 public MimeMessage getMimeMessage() {
443 return mimeMessage;
444 }
445
446 public String getReturnPath() {
447 return returnPath;
448 }
449
450 public boolean hasEnvelopeTo() {
451 return envelopeTo.length > 0;
452 }
453
454 public InternetAddress[] getEnvelopeTo() {
455 return envelopeTo;
456 }
457 }
458
459 }