View Javadoc

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