OSDN Git Service

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