001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.mail; 017 018import java.util.Properties; 019import java.util.List; 020import java.util.ArrayList; 021import java.util.Locale; // 6.3.8.0 (2015/09/11) 022 023import jakarta.mail.Session; 024import jakarta.mail.Store; 025import jakarta.mail.Folder; 026import jakarta.mail.Message; 027import jakarta.mail.Flags; 028import jakarta.mail.MessagingException; 029import jakarta.mail.NoSuchProviderException; 030import jakarta.mail.search.SearchTerm; 031import jakarta.mail.search.SubjectTerm; 032import jakarta.mail.search.FromStringTerm; 033import jakarta.mail.search.BodyTerm; 034import jakarta.mail.search.HeaderTerm; 035import jakarta.mail.search.AndTerm; 036 037import org.opengion.fukurou.util.StringUtil ; 038import org.opengion.fukurou.util.HybsEntry ; 039import org.opengion.fukurou.system.LogWriter; 040 041/** 042 * MailRX は、POP3/IMAPプロトコルによるメール受信プログラムです。 043 * 044 * メールへの接続条件(host,user,passwd など)と、選択条件(matchTermなど)を指定し、 045 * MailReceiveListener をセットして、start() メソッドを呼びます。 046 * 実際のメール処理は、MailReceiveListener を介して、1メールずつ処理します。 047 * 添付ファイルを処理する場合は、MailAttachFiles クラスを使用します。 048 * 049 * host メールサーバー(必須) 050 * user メールを取得するログインユーザー(必須) 051 * passwd メールを取得するログインパスワード(必須) 052 * protocol 受信サーバーのプロトコル[imap/pop3]を指定(初期値:{@og.value #PROTOCOL}) 053 * port 受信サーバーのポートを指定(初期値:{@og.value #PORT}) 054 * useSSL SSL接続するかどうか[true:する/false:しない]を指定(初期値:false:しない) 055 * mbox 受信サーバーのメールボックスを指定(初期値:{@og.value #MBOX}) 056 * maxRowCount 受信メールの最大取り込み件数(初期値:{@og.value #MAX_ROW_COUNT})(0:[無制限]) 057 * charset メールのデフォルトエンコード(初期値:{@og.value #CHARSET}) 058 * matchTerm 受信メールを選択する条件のMINEntryオブジェクト 059 * delete 検索後、メールをサーバーから削除するかどうかを、true/falseで指定(初期値:{@og.value #DELETE_MESSAGE})。 060 * 061 * ※ 6.3.8.0 (2015/09/11) 062 * useSSL属性は、protocolに、pop3s/imaps を指定した場合、 063 * 自動的に、ture に設定するようにしています。 064 * 065 * @version 4.0 066 * @author Kazuhiko Hasegawa 067 * @since JDK5.0, 068 */ 069public class MailRX { 070 071 /** 受信メールの最大取り込み件数を指定します 「={@value}」 */ 072 public static final int MAX_ROW_COUNT = 100 ; 073 074 /** 検索後、メールをサーバーから削除するかどうかを、true/falseで指定します 「={@value}」 */ 075 public static final boolean DELETE_MESSAGE = false ; 076 077 /** メールサーバーのデフォルトプロトコル 「={@value}」 */ 078 public static final String PROTOCOL = "pop3" ; 079 080 /** メールサーバーのデフォルトポート番号 「={@value}」 */ 081 public static final int PORT = -1 ; 082 083 /** メールサーバーのデフォルトメールボックス 「={@value}」。 */ 084 public static final String MBOX = "INBOX" ; 085 086 /** メールのデフォルトエンコード 「={@value}」 087 * Windwos-31J , MS932 , UTF-8 , ISO-2022-JP を指定します。 088 */ 089 public static final String CHARSET = "ISO-2022-JP" ; 090 091 /** メール受信毎に発生するイベントを伝えるリスナーを登録します。 */ 092 private MailReceiveListener listener ; 093 094 private String host ; 095 private String user ; 096 private String passwd ; 097 private String protocol = PROTOCOL; 098 private int port = PORT; 099 private boolean isUseSSL ; // 6.3.8.0 (2015/09/11) 100 private String mbox = MBOX; 101 private boolean deleteFlag = DELETE_MESSAGE; 102 private String charset = CHARSET; 103 private int maxRowCount = MAX_ROW_COUNT; 104 105 private final List<HybsEntry> matchList = new ArrayList<>(); 106 private boolean debug ; 107 108 /** 109 * デフォルトコンストラクター 110 * 111 * @og.rev 8.5.3.2 (2023/10/13) JDK21対応。警告: デフォルトのコンストラクタの使用で、コメントが指定されていません 112 */ 113 public MailRX() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 114 115 /** 116 * レシーバーを開始します。 117 * 118 * @og.rev 6.3.8.0 (2015/09/11) SSL接続するかどうかを指定するパラメータを追加します。 119 * 120 * @throws MessagingException レシーバー処理中に、なんらかのエラーが発生した場合。 121 * @throws NoSuchProviderException なんらかのエラーが発生した場合。 122 */ 123 public void start() throws MessagingException,NoSuchProviderException { 124 125 // パラメータの解析、取得 126 debugMsg( "パラメータの解析、取得" ); 127 128 // 指定の条件にマッチしたメッセージのみ抜き出す為の、SearchTerm オブジェクトの作成 129 // 6.3.8.0 (2015/09/11) IMAPの場合、条件の有無で、メッセージの取得方法を変える必要がる。 130 SearchTerm srchTerm = null; 131 if( !matchList.isEmpty() ) { 132 debugMsg( "指定の条件にマッチしたメッセージのみ抜き出す条件を設定します。" ); 133// final HybsEntry[] matchs = matchList.toArray( new HybsEntry[matchList.size()] ); 134 final HybsEntry[] matchs = matchList.toArray( new HybsEntry[0] ); // 8.5.4.2 (2024/01/12) PMD 7.0.0 OptimizableToArrayCall 対応 135 final SearchTerm[] term = new SearchTerm[matchs.length]; // 8.5.4.2 (2024/01/12) PMD 7.0.0 LocalVariableCouldBeFinal 136 for( int i=0; i<matchs.length; i++ ) { 137 final String key = matchs[i].getKey(); 138 if( "Subject".equalsIgnoreCase( key ) ) { 139 term[i] = new SubjectTerm( matchs[i].getValue() ); 140 } 141 else if( "From".equalsIgnoreCase( key ) ) { 142 term[i] = new FromStringTerm( matchs[i].getValue() ); 143 } 144 else if( "Body".equalsIgnoreCase( key ) ) { 145 term[i] = new BodyTerm( matchs[i].getValue() ); 146 } 147 else { 148 term[i] = new HeaderTerm( key,matchs[i].getValue() ); 149 } 150 } 151 srchTerm = new AndTerm( term ); 152 } 153 154 // 空の properties を設定。気休め程度に、初期値を設定しておきます。 155 debugMsg( "空の properties を設定" ); 156 final Properties prop = new Properties(); 157 prop.setProperty("mail.mime.charset" , charset ); 158 prop.setProperty("mail.mime.decodetext.strict" , "false" ); 159 prop.setProperty("mail.mime.address.strict" , "false" ); 160 161 // 6.3.8.0 (2015/09/11) SSL接続するかどうかを指定するパラメータを追加します。 162 if( isUseSSL ) { 163 if( protocol.contains( "pop3" ) ) { // pop3/pop3s 164 prop.setProperty("mail.pop3.socketFactory.class" , "javax.net.ssl.SSLSocketFactory" ); 165 prop.setProperty("mail.pop3.socketFactory.fallback" , "false" ); 166 prop.setProperty("mail.pop3.socketFactory.port" , String.valueOf( port ) ); // 995 167 } 168 // google の IMAP の場合は、下記設定なしでも、protocol=imaps のみで接続できた。 169 else if( protocol.contains( "imap" ) ) { // imap/imaps 170 prop.setProperty("mail.imap.ssl.enable" , "true" ); 171 prop.setProperty("mail.imap.ssl.socketFactory.class" , "DummySSLSocketFactory" ); 172 prop.setProperty("mail.imap.ssl.socketFactory.fallback" , "false" ); 173 } 174 } 175 176 // session を取得 177 debugMsg( "session を取得" ); 178 final Session session = Session.getInstance( prop, null ); 179 180 Store store = null; 181 Folder folder = null; 182 try { 183 // store の取得 184 debugMsg( "store の取得 protocol=",protocol ); 185 store = session.getStore( protocol ); 186 187 // サーバーと connect します。 188 debugMsg( "サーバーと connect します。" ); 189 store.connect( host, port, user, passwd ); 190 191 // folder の取得 192 debugMsg( "folder の取得" ); 193 folder = store.getFolder( mbox ); 194 if( deleteFlag ) { 195 folder.open( Folder.READ_WRITE ); 196 } 197 else { 198 folder.open( Folder.READ_ONLY ); 199 } 200 201 // メッセージ情報の取得 202 debugMsg( "メッセージ情報の取得" ); 203 // 6.3.8.0 (2015/09/11) IMAPの場合、条件の有無で、メッセージの取得方法を変える必要がる。 204 final Message[] message = srchTerm == null ? folder.getMessages() : folder.search( srchTerm ) ; 205 206 final int len = message.length; // 6.1.0.0 (2014/12/26) refactoring 207 for( int i=0; i<len && i<maxRowCount; i++ ) { 208 final MailMessage mailMessage = new MailMessage( message[i],host,user ); 209 debugMsg( "[" , String.valueOf(i) , "]" , mailMessage.getMessageID() , " 受信中" ); 210 211 // メールの削除[true/false]:先にフラグを立てているので、エラーでも削除されます。 212 // 6.3.8.0 (2015/09/11) deleteFlag で、READ_ONLY かどうかを指定しているため、セットの判定を入れます。 213 if( deleteFlag ) { 214 message[i].setFlag( Flags.Flag.DELETED, true ); 215 } 216 217 boolean okFlag = true; 218 if( listener != null ) { 219 // メール本体の処理 220 okFlag = listener.receive( mailMessage ); 221 } 222 223 // 受領確認の返信メール 224 final String notifyTo = mailMessage.getNotificationTo() ; 225 if( okFlag && notifyTo != null ) { 226 final MailTX tx = new MailTX( host ); 227 tx.setFrom( user ); 228 tx.setTo( StringUtil.csv2Array( notifyTo ) ); 229 tx.setSubject( "受領:" + mailMessage.getSubject() ); 230 tx.setMessage( mailMessage.getContent() ); 231 tx.sendmail(); 232 } 233 } 234 } 235 finally { 236 // セッション終了 237 debugMsg( "セッション終了処理" ); 238 if( folder != null ) { 239 folder.close( deleteFlag ); // true の場合は、終了時に実際に削除します。 240 } 241 if( store != null ) { 242 store.close(); 243 } 244 } 245 } 246 247 /** 248 * メールサーバーをセットします(必須)。 249 * 250 * @param host メールサーバー 251 * @throws IllegalArgumentException 引数が null の場合。 252 */ 253 public void setHost( final String host ) { 254 if( host == null ) { 255 final String errMsg = "host に null はセット出来ません。"; 256 throw new IllegalArgumentException( errMsg ); 257 } 258 259 this.host = host; 260 } 261 262 /** 263 * 受信ユーザーをセットします(必須)。 264 * 265 * @param user 受信ユーザー 266 * @throws IllegalArgumentException 引数が null の場合。 267 */ 268 public void setUser( final String user ) { 269 if( user == null ) { 270 final String errMsg = "user に null はセット出来ません。"; 271 throw new IllegalArgumentException( errMsg ); 272 } 273 this.user = user; 274 } 275 276 /** 277 * パスワードをセットします(必須)。 278 * 279 * @param passwd パスワード 280 * @throws IllegalArgumentException 引数が null の場合。 281 */ 282 public void setPasswd( final String passwd ) { 283 if( passwd == null ) { 284 final String errMsg = "passwd に null はセット出来ません。"; 285 throw new IllegalArgumentException( errMsg ); 286 } 287 this.passwd = passwd; 288 } 289 290 /** 291 * 受信プロトコル(pop3/imap等)をセットします(初期値:{@og.value #PROTOCOL})。 292 * 293 * protocolに、pop3s/imaps を指定した場合、 294 * useSSL属性は、自動的に、ture に設定されます。 295 * 296 * @param prtcol 受信プロトコル名 297 * @throws IllegalArgumentException 引数が null の場合。 298 * @see #PROTOCOL 299 */ 300 public void setProtocol( final String prtcol ) { 301 if( prtcol == null ) { 302 final String errMsg = "protocol に null はセット出来ません。"; 303 throw new IllegalArgumentException( errMsg ); 304 } 305 protocol = prtcol.toLowerCase( Locale.JAPAN ); 306 307 // 6.3.8.0 (2015/09/11) 登録順に影響されない様に、注意 308 if( port < 0 ) { // 未設定 309 if( "pop3".equalsIgnoreCase( protocol ) ) { port = 110; } 310 if( "imap".equalsIgnoreCase( protocol ) ) { port = 143; isUseSSL = true; } 311 if( "pop3s".equalsIgnoreCase( protocol ) ) { port = 995; } 312 if( "imaps".equalsIgnoreCase( protocol ) ) { port = 993; isUseSSL = true; } 313 } 314 } 315 316 /** 317 * ポート番号をセットします(初期値:{@og.value #PORT})。 318 * 319 * portが、-1 の場合は、protocol に応じたポートが使用されます。 320 * pop3:110 , imap:143 , pop3s:995 , imaps:993 321 * 322 * @param port ポート番号 323 * @see #PORT 324 */ 325 public void setPort( final int port ) { 326 this.port = port; 327 } 328 329 /** 330 * SSL接続するかどうかをセットします(初期値:false:しない)。 331 * 332 * protocolに、pop3s/imaps を指定した場合、 333 * useSSL属性は、自動的に、ture に設定されます。 334 * 335 * @og.rev 6.3.8.0 (2015/09/11) SSL接続するかどうかを指定するパラメータを追加します。 336 * 337 * @param isSSL SSL接続するかどうか[true:する/false:しない]を指定 338 */ 339 public void useSSL( final boolean isSSL ) { 340 // 6.3.8.0 (2015/09/11) 登録順に影響されない様に、注意 341 isUseSSL = isSSL || "pop3s".equalsIgnoreCase( protocol ) || "imaps".equalsIgnoreCase( protocol ); 342 } 343 344 /** 345 * 受信メイルボックスをセットします(初期値:{@og.value #MBOX})。 346 * 347 * @param mbox 受信メイルボックス名 348 * @throws IllegalArgumentException 引数が null の場合。 349 * @see #MBOX 350 */ 351 public void setMbox( final String mbox ) { 352 if( mbox == null ) { 353 final String errMsg = "mbox に null はセット出来ません。"; 354 throw new IllegalArgumentException( errMsg ); 355 } 356 this.mbox = mbox; 357 } 358 359 /** 360 * メール受信毎に発生するイベントを伝えるリスナーをセットします。 361 * 362 * @param listener MailReceiveリスナー 363 */ 364 public void setMailReceiveListener( final MailReceiveListener listener ) { 365 this.listener = listener; 366 } 367 368 /** 369 * メッセージをメールサーバーから削除するかどうかをセットします(初期値:{@og.value #DELETE_MESSAGE})。 370 * 371 * @param deleteFlag 削除するかどうか[true:行う/false:行わない] 372 * @see #DELETE_MESSAGE 373 */ 374 public void setDelete( final boolean deleteFlag ) { 375 this.deleteFlag = deleteFlag; 376 } 377 378 /** 379 * 文字エンコーディングをセットします(初期値:{@og.value #CHARSET})。 380 * 381 * 文字エンコーディングには、Windwos-31J , MS932 , ISO-2022-JP を指定できます。 382 * 初期値は、SystemResource.properties ファイルの MAIL_DEFAULT_CHARSET 属性で 383 * 設定できます。 384 * 385 * @param charset 文字エンコーディング 386 * @throws IllegalArgumentException 引数が null の場合。 387 * @see #CHARSET 388 */ 389 public void setCharset( final String charset ) { 390 if( charset == null ) { 391 final String errMsg = "charset に null はセット出来ません。"; 392 throw new IllegalArgumentException( errMsg ); 393 } 394 this.charset = charset; 395 } 396 397 /** 398 * 最大取り込み件数をセットします(初期値:{@og.value #MAX_ROW_COUNT})(0:[無制限])。 399 * 400 * @og.rev 5.5.8.5 (2012/11/27) 0を無制限として処理します。 401 * 402 * @param maxCount 最大取り込み件数 403 * @see #MAX_ROW_COUNT 404 */ 405 public void setMaxRowCount( final int maxCount ) { 406 maxRowCount = maxCount>0 ? maxCount : Integer.MAX_VALUE ; // 6.0.2.5 (2014/10/31) refactoring 407 } 408 409 /** 410 * メール検索する場合のマッチ条件のキーと値の HybsEntry をセットします。 411 * Subject,From,Body,それ以外は、Header 文字列をキーにします。 412 * 413 * @param matchTerm HybsEntryオブジェクト 414 */ 415 public void addMatchTerm( final HybsEntry matchTerm ) { 416 matchList.add( matchTerm ); 417 } 418 419 /** 420 * デバッグ情報の表示を行うかどうかをセットします。 421 * 422 * @param debug 有無[true/false] 423 */ 424 public void setDebug( final boolean debug ) { 425 this.debug = debug; 426 } 427 428 /** 429 * デバッグ情報の表示を行います。 430 * 実際の処理は、debug フラグに設定値によります。 431 * 432 * @param msgs デバッグ情報(可変長引数) 433 */ 434 private void debugMsg( final String... msgs ) { 435 if( debug ) { 436 for( final String msg : msgs ) { 437 System.out.print( msg ); 438 } 439 System.out.println(); 440 } 441 } 442 443 /** 444 * コマンドから実行できる、テスト用の main メソッドです。 445 * 446 * Usage: java org.opengion.fukurou.mail.MailTX MailRX host user passwd [saveDir] 447 * で、複数の添付ファイルを送付することができます。 448 * 449 * @og.rev 6.3.9.1 (2015/11/27) A method/constructor shouldnt explicitly throw java.lang.Exception(PMD)。 450 * 451 * @param args 引数配列 452 * @throws MessagingException なんらかのエラーが発生した場合。 453 */ 454 public static void main( final String[] args ) throws MessagingException { 455 if( args.length<3 ) { 456 LogWriter.log("Usage: java org.opengion.fukurou.mail.MailRX host user passwd [saveDir]"); 457 System.exit(1); 458 } 459 final String dir = args.length == 4 ? args[3] : null; 460 461 final MailRX recive = new MailRX(); 462 463 recive.setHost( args[0] ); 464 recive.setUser( args[1] ); 465 recive.setPasswd( args[2] ); 466 recive.setCharset( "ISO-2022-JP" ); 467 468 final MailReceiveListener listener = new MailReceiveListener() { 469 /** 470 * メール受信処理で、1メール受信ごとに呼び出されます。 471 * 処理結果を、boolean で返します。 472 * 473 * @param message MailMessageオブジェクト 474 * @return 処理結果(正常:true / 異常:false) 475 */ 476 public boolean receive( final MailMessage message ) { 477 System.out.println( message.getSimpleMessage() ); 478 479 if( dir != null ) { 480 message.saveSimpleMessage( dir ); 481 } 482 return true ; 483 } 484 }; 485 recive.setMailReceiveListener( listener ); 486 487 recive.start(); 488 } 489}