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.3 2005/01/29 23:09:36 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
158 if (username != null && !"".equals(username) && password != null && !"".equals(password)) {
159 prop.put("mail.smtp.auth", "true");
160 }
161 Session session = Session.getInstance(prop);
162
163 Transport transport = null;
164 try {
165
166 log.debug("SMTPサーバ[" + host + "]に接続します。");
167 transport = session.getTransport(protocol);
168 transport.connect(host, port, username, password);
169 log.debug("SMTPサーバ[" + host + "]に接続しました。");
170
171 for (int i = 0; i < mmws.length; i++) {
172 MimeMessage mimeMessage = mmws[i].getMimeMessage();
173
174 String returnPath = mmws[i].getReturnPath();
175 if (returnPath != null) {
176 session.getProperties().put(RETURN_PATH_KEY, returnPath);
177 log.debug("Return-Path[" + returnPath + "]を設定しました。");
178 }
179
180 mimeMessage.setSentDate(new Date());
181 mimeMessage.saveChanges();
182
183
184 log.debug("メールを送信します。");
185 if (mmws[i].hasEnvelopeTo()) {
186 log.debug("メールはenvelope-toアドレスに送信されます。");
187 transport.sendMessage(mimeMessage, mmws[i].getEnvelopeTo());
188 } else {
189 transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
190 }
191 log.debug("メールを送信しました。");
192
193
194 if (returnPath != null) {
195 session.getProperties().remove(RETURN_PATH_KEY);
196 log.debug("Return-Path設定をクリアしました。");
197 }
198 }
199 } catch (AuthenticationFailedException ex) {
200 log.error("SMTPサーバ[" + host + "]への接続認証に失敗しました。", ex);
201 throw new MailAuthenticationException(ex);
202 } catch (MessagingException ex) {
203 log.error("メールの送信に失敗しました。", ex);
204 throw new MailSendException("メールの送信に失敗しました。", ex);
205 } finally {
206 if (transport != null && transport.isConnected()) {
207 log.debug("SMTPサーバ[" + host + "]との接続を切断します。");
208 try {
209
210 transport.close();
211 } catch (MessagingException e) {
212 log.error("SMTPサーバ[" + host + "]との接続切断に失敗しました。", e);
213 throw new MailException("SMTPサーバ[" + host + "]との接続切断に失敗しました。");
214 }
215 log.debug("SMTPサーバ[" + host + "]との接続を切断しました。");
216 }
217 }
218 }
219
220 /***
221 * 新しいMimeMessageオブジェクトを生成します。<br>
222 * messageIdプロパティがセットされている場合、OMLMimeMessageのインスタンスを生成します。
223 *
224 * @return 新しいMimeMessageオブジェクト
225 */
226 private MimeMessage createMimeMessage(Session session) {
227 if (isMessageIdCustomized()) {
228 return new OMLMimeMessage(session, messageId);
229 }
230 return new MimeMessage(session);
231 }
232
233 /***
234 * Message-Idヘッダのドメイン部分を独自にセットしているかどうか判定します。
235 *
236 * @return Message-Idヘッダのドメイン部分を独自にセットしている場合 true
237 */
238 private boolean isMessageIdCustomized() {
239 return messageId != null;
240 }
241
242 /***
243 * エンコーディングに使用する文字コードを返します。
244 *
245 * @return エンコーディングに使用する文字コード
246 */
247 public String getCharset() {
248 return charset;
249 }
250
251 /***
252 * メールの件名や本文のエンコーディングに使用する文字コードを指定します。
253 * デフォルトは<code>ISO-2022-JP</code>です。
254 * <p>
255 * 日本語環境で利用する場合は通常変更する必要はありません。
256 *
257 * @param charset エンコーディングに使用する文字コード
258 */
259 public void setCharset(String charset) {
260 this.charset = charset;
261 }
262
263 /***
264 * セットされたSMTPサーバのホスト名、またはIPアドレスを返します。
265 *
266 * @return SMTPサーバのホスト名、またはIPアドレス
267 */
268 public String getHost() {
269 return host;
270 }
271
272 /***
273 * SMTPサーバのホスト名、またはIPアドレスをセットします。
274 * デフォルトは localhost です。
275 *
276 * @param host SMTPサーバのホスト名、またはIPアドレス
277 */
278 public void setHost(String host) {
279 this.host = host;
280 }
281
282 /***
283 * @return SMTPサーバ認証パスワード
284 */
285 public String getPassword() {
286 return password;
287 }
288
289 /***
290 * SMTPサーバの接続認証が必要な場合にパスワードをセットします。
291 *
292 * @param password SMTPサーバ認証パスワード
293 */
294 public void setPassword(String password) {
295 this.password = password;
296 }
297
298 /***
299 * @return SMTPサーバのポート番号
300 */
301 public int getPort() {
302 return port;
303 }
304
305 /***
306 * SMTPサーバのポート番号をセットします。
307 *
308 * @param port SMTPサーバのポート番号
309 */
310 public void setPort(int port) {
311 this.port = port;
312 }
313
314 /***
315 * プロトコルを返します。
316 *
317 * @return プロトコル
318 */
319 public String getProtocol() {
320 return protocol;
321 }
322
323 /***
324 * プロトコルをセットします。デフォルトは「smtp」。
325 *
326 * @param protocol プロトコル
327 */
328 public void setProtocol(String protocol) {
329 this.protocol = protocol;
330 }
331
332 /***
333 * @return Return-Pathアドレス
334 */
335 public String getReturnPath() {
336 return returnPath;
337 }
338
339 /***
340 * Return-Pathアドレスをセットします。
341 * <p>
342 * 送信するMailインスタンスに指定されたFromアドレス以外のアドレスをReturn-Pathとしたい場合に使用します。
343 * ここでセットされたReturn-Pathより、MailインスタンスにセットされたReturn-Pathが優先されます。
344 *
345 * @param returnPath Return-Pathアドレス
346 */
347 public void setReturnPath(String returnPath) {
348 this.returnPath = returnPath;
349 }
350
351 /***
352 * @return SMTPサーバ認証ユーザ名
353 */
354 public String getUsername() {
355 return username;
356 }
357
358 /***
359 * SMTPサーバの接続認証が必要な場合にユーザ名をセットします。
360 *
361 * @param username SMTPサーバ認証ユーザ名
362 */
363 public void setUsername(String username) {
364 this.username = username;
365 }
366
367 /***
368 * SMTPサーバとの接続タイムアウトをセットします。
369 * 単位はミリ秒。デフォルトは5,000ミリ秒(5秒)です。
370 * <p>
371 * -1を指定すると無限大になりますが、お薦めしません。
372 *
373 * @since 1.1.4
374 * @param connectionTimeout SMTPサーバとの接続タイムアウト
375 */
376 public void setConnectionTimeout(int connectionTimeout) {
377 this.connectionTimeout = connectionTimeout;
378 }
379
380 /***
381 * SMTPサーバへの送信時のタイムアウトをセットします。
382 * 単位はミリ秒。デフォルトは5,000ミリ秒(5秒)です。<br>
383 * 送信時にタイムアウトすると、<code>com.ozacc.mail.MailSendException</code>がスローされます。
384 * <p>
385 * -1を指定すると無限大になりますが、お薦めしません。
386 *
387 * @since 1.1.4
388 * @param readTimeout SMTPサーバへの送受信時のタイムアウト
389 */
390 public void setReadTimeout(int readTimeout) {
391 this.readTimeout = readTimeout;
392 }
393
394 /***
395 * 生成されるMimeMessageに付けられるMessage-Idヘッダのドメイン部分を指定します。<br>
396 * 指定されない場合(nullや空文字列の場合)は、JavaMailがMessage-Idヘッダを生成します。
397 * JavaMailが生成する「JavaMail.実行ユーザ名@ホスト名」のMessage-Idを避けたい場合に、このメソッドを使用します。
398 * <p>
399 * messageIdプロパティがセットされている場合、Mailから生成されるMimeMessageのMessage-Idには
400 * <code>タイムスタンプ + ランダムに生成される16桁の数値 + ここでセットされた値</code>
401 * が使用されます。
402 * <p>
403 * 生成されるMessage-Idの例。 (実際の数値部分は送信メール毎に変わります)<ul>
404 * <li>messageIdに'example.com'を指定した場合・・・1095714924963.5619528074501343@example.com</li>
405 * <li>messageIdに'@example.com'を指定した場合・・・1095714924963.5619528074501343@example.com (上と同じ)</li>
406 * <li>messageIdに'OML@example.com'を指定した場合・・・1095714924963.5619528074501343.OML@example.com</li>
407 * <li>messageIdに'.OML@example.com'を指定した場合・・・1095714924963.5619528074501343.OML@example.com (上と同じ)</li>
408 * </ul>
409 * <p>
410 * <strong>注:</strong> このMessage-Idは<code>send(Mail)</code>か<code>send(Mail[])</code>メソッドが呼びだれた時にのみ有効です。MimeMessageを直接送信する場合には適用されません。
411 *
412 * @param messageId メールに付けられるMessage-Idヘッダのドメイン部分
413 * @throws IllegalArgumentException @を複数含んだ文字列を指定した場合
414 */
415 public void setMessageId(String messageId) {
416 if (messageId == null || messageId.length() < 1) {
417 return;
418 }
419
420 String[] parts = messageId.split("@");
421 if (parts.length > 2) {
422 throw new IllegalArgumentException("messageIdプロパティに'@'を複数含むことはできません。[" + messageId
423 + "]");
424 }
425
426 this.messageId = messageId;
427 }
428
429 /***
430 * MimeMessageインスタンスと、そのメールに対応するReturn-Path、envelope-toアドレスをラップするクラス。
431 *
432 * @author Tomohiro Otsuka
433 * @version $Id: SendMailImpl.java,v 1.7.2.3 2005/01/29 23:09:36 otsuka Exp $
434 */
435 private static class MimeMessageWrapper {
436
437 private MimeMessage mimeMessage;
438
439 private String returnPath;
440
441 private InternetAddress[] envelopeTo;
442
443 public MimeMessageWrapper(MimeMessage mimeMessage, String returnPath) {
444 this.mimeMessage = mimeMessage;
445 this.returnPath = returnPath;
446 }
447
448 public MimeMessageWrapper(MimeMessage mimeMessage, String returnPath,
449 InternetAddress[] envelopeTo) {
450 this.mimeMessage = mimeMessage;
451 this.returnPath = returnPath;
452 this.envelopeTo = envelopeTo;
453 }
454
455 public MimeMessage getMimeMessage() {
456 return mimeMessage;
457 }
458
459 public String getReturnPath() {
460 return returnPath;
461 }
462
463 public boolean hasEnvelopeTo() {
464 return envelopeTo.length > 0;
465 }
466
467 public InternetAddress[] getEnvelopeTo() {
468 return envelopeTo;
469 }
470 }
471
472 }