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