001package org.opengion.plugin.cloud; 002 003import java.io.IOException; 004import java.text.SimpleDateFormat; 005import java.util.ArrayList; 006import java.util.Calendar; 007import java.util.Date; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011 012import org.opengion.fukurou.db.DBUtil; 013import org.opengion.hayabusa.common.HybsSystem; 014import org.opengion.hayabusa.common.HybsSystemException; 015import org.opengion.hayabusa.mail.MailManager_DB; 016import org.opengion.hayabusa.mail.MailPattern; 017 018import com.fasterxml.jackson.core.JsonProcessingException; 019import com.fasterxml.jackson.databind.ObjectMapper; 020import com.sendgrid.Method; 021import com.sendgrid.Request; 022import com.sendgrid.SendGrid; 023 024/** 025 * パッチによるメール送信の実装クラスです。 026 * 送信デーモンはパラメータテーブル(GE30)を監視して、新規のデータが登録されたら、 027 * そのデータをパラメータとしてメール合成処理メソッドに渡して合成を行って送信します。 028 * 最後に、処理結果を受取って、パラメータテーブルの状況フラグを送信済/送信エラーに更新します。 029 * エラーが発生した場合、エラーテーブルにエラーメッセージを書き込みます。 030 * 031 * hayabusa.mailの標準クラスを継承して作成しています。 032 * 基本的な動作は同じですが、メール送信にSMTPではなくsendGridのAPIを利用します。 033 * MAIL_SENDGRID_APIKEYをシステムリソースとして登録する必要があります。 034 * 035 * 一時的に利用できなくなる事を想定して、 036 * 一定時間の間(ハードコーディングで10分としている)はエラーが発生しても再送を試みるようにします。 037 * 038 * このクラスをコンパイルするためにはsendgrid-java-4.1.1.jar,java-http-client-4.1.0.jarが必要です。 039 * 実行にはhamcrest-core-1.1.jar,httpclient-4.5.2.jar,httpcore-4.4.4.jar,mockito-core-1.10.19.jar,objenesis-2.1.jar 040 * ,jackson-annotations-2.5.3.jar,jackson-core-2.5.3.jar,jackson-databind-2.5.3.jarが必要です。 041 * 042 * @og.group メールモジュール 043 * 044 * @og.rev 5.9.26.0 (2017/11/02) 新規作成 045 * @author T.OTA 046 * @sinse JDK1.7 047 * 048 */ 049public class MailManager_DB_SendGridAPI extends MailManager_DB { 050 private static final String selGE30DYSET = "SELECT DYSET FROM GE30 WHERE UNIQ = ?"; // 2017/10/27 ADD 登録時刻の取得 051 // SendGridのAPIキー 052 private static final String SENDGRID_APIKEY = HybsSystem.sys("MAIL_SENDGRID_APIKEY"); 053 // メール送信先のtoリスト 054 private ArrayList<String> toList = new ArrayList<String>(); 055 // メール送信先のccリスト 056 private ArrayList<String> ccList = new ArrayList<String>(); 057 // メール送信先のbccリスト 058 private ArrayList<String> bccList = new ArrayList<String>(); 059 060 /** 061 * バッチより呼出のメインメソッドです。 062 * パラメータテーブル(GE30)を監視します。 063 * 新規のデータが登録されたら、メール文を合成して送信を行います。 064 * エラーが発生した場合、エラーテーブルにエラーメッセージを書き込みます。 065 * 066 * @param systemId システムID 067 */ 068 @Override 069 public void sendDBMail( final String systemId ){ 070 // パラメータテーブルよりバッチでセットしたデータを取得します。 071 String[][] ge30datas = DBUtil.dbExecute( selGE30, new String[]{ systemId, HybsSystem.getDate( "yyyyMMddHHmmss" ) }, appInfo, DBID ); // 5.9.18.0 (2017/03/02) 072 073 // 2017/10/27 ADD SendGrid利用の追加対応 074 String timePre1Hour = ""; 075 // タイムスタンプの設定 076 timePre1Hour = getTimePre1Hour(); 077 078 int ge30Len = ge30datas.length; 079 080 for( int i=0; i < ge30Len; i++ ) { 081 String fgj = SNED_OK; 082 try { 083 Map<String, String> initParam = makeParamMap( systemId, ge30datas[i] ); 084 create( initParam ); 085 send(); // 合成されたメール文書、宛先で送信処理を行います。 086 errMsgList.addAll( getErrList() ); 087 } 088 catch( RuntimeException rex ) { 089 fgj = SNED_NG; 090 errMsgList.add( "メール送信失敗しました。パラメータキー:" + ge30datas[i][GE30_UNIQ] + " " + rex.getMessage() ); 091 } 092 finally { 093 if(fgj != SNED_NG){ 094 commitParamTable( ge30datas[i][GE30_UNIQ], fgj ); 095 }else{ 096 // エラーレコードの登録日時を取得 097 String[][] rec = DBUtil.dbExecute( selGE30DYSET, new String[]{ge30datas[i][GE30_UNIQ]}, appInfo, DBID); 098 String DYSET = rec[0][0]; 099 100 if(DYSET.compareTo(timePre1Hour) < 0){ 101 // 登録から一定時間以上のエラーをエラーに更新 102 commitParamTable( ge30datas[i][GE30_UNIQ], fgj ); 103 } 104 else { 105 // それ以外は再送を試みる 106 commitParamTable( ge30datas[i][GE30_UNIQ], "1" ); 107 108 } 109 } 110 111 if ( ! errMsgList.isEmpty() ) { 112 writeErrorTable( ge30datas[i][GE30_UNIQ], systemId, errMsgList ); 113 errMsgList.clear(); 114 } 115 } 116 } 117 } 118 119 /** 120 * 1時間前のタイムスタンプを取得 121 * 122 * @return タイムスタンプ(1時間前) 123 */ 124 private String getTimePre1Hour(){ 125 Date date = new Date(); 126 Calendar call = Calendar.getInstance(); 127 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); 128 call.setTime(date); 129 // sendGridが一時的に使えなくなる場合を考慮 130 // 10分間は再送を試みる 131 call.add(Calendar.MINUTE, -10); 132 133 return sdf.format(call.getTime()); 134 } 135 136 /** 137 * SendGridApiを利用して、メール送信を行うメソッドです。 138 * 139 */ 140 @Override 141 public void send(){ 142 // 宛先 143 List<String> invalidAddrBuf = new ArrayList<String>(); 144 setMailDst(invalidAddrBuf); 145 146 try{ 147 SendGrid sg = new SendGrid(SENDGRID_APIKEY); 148 149 Request request = new Request(); 150 request.setMethod(Method.POST); 151 request.setEndpoint("mail/send"); 152 153 // SengGrid向けJsonの設定 154 request.setBody(makeJson()); 155 156 // メール送信要求 157 sg.api(request); 158 159 // 送信結果を履歴テーブル、宛先テーブルにセットします。 160 commitMailDB(); 161 162 }catch(IOException e){ 163 String errMsg = "送信時にエラー発生しました。" + e.getMessage(); 164 throw new RuntimeException( errMsg,e ); 165 } 166 } 167 168 /** 169 * SendGrid向けのJsonを生成します。 170 * @return JSONデータ 171 */ 172 private String makeJson(){ 173 String rtnJson = ""; 174 Map<Object,Object> jsonMap = new HashMap<Object, Object>(); 175 // 送信先の設定 176 Map<String,List<Map<String,String>>> sendMap = new HashMap<String,List<Map<String,String>>>(); 177 sendMap.put("to", setSendList(toList)); 178 if(!ccList.isEmpty()){ 179 sendMap.put("cc", setSendList(ccList)); 180 } 181 if(!bccList.isEmpty()){ 182 sendMap.put("bcc", setSendList(bccList)); 183 } 184 jsonMap.put("personalizations", new Map[]{sendMap}); 185 // タイトル 186 jsonMap.put("subject",getTitle()); 187 // 送信元 188 jsonMap.put("from", setMap("email",getFromAddr())); 189 // 内容 190 Map<String,String> contentMap = new HashMap<String,String>(); 191 contentMap.put("type","text/plain"); 192 contentMap.put("value",getContent()); 193 jsonMap.put("content", new Map[]{contentMap}); 194 195 ObjectMapper mapper = new ObjectMapper(); 196 197 try{ 198 rtnJson = mapper.writeValueAsString(jsonMap); 199 }catch(JsonProcessingException e){ 200 String errMsg = "JSONの生成に失敗しました。" + e; 201 throw new HybsSystemException(errMsg); 202 } 203 204 return rtnJson; 205 } 206 207 /** 208 * Map格納用メソッド 209 * 210 * @param val1 211 * @param val2 212 * @return マップ 213 */ 214 private Map<Object,Object> setMap(Object val1, Object val2){ 215 Map<Object,Object> rtnMap = new HashMap<Object,Object>(); 216 rtnMap.put(val1,val2); 217 return rtnMap; 218 } 219 220 /** 221 * メール送信先リストをJSON用リストに設定 222 * 223 * @param list 224 * @return JSON用リスト 225 */ 226 private List<Map<String,String>> setSendList(ArrayList<String> list){ 227 // toリスト 228 List<Map<String,String>> rtnList = new ArrayList<Map<String,String>>(); 229 for(String str: list){ 230 Map<String,String> map = new HashMap<String,String>(); 231 map.put("email", str); 232 rtnList.add(map); 233 } 234 return rtnList; 235 } 236 237 /** 238 * 宛先マップを元に、送信オブジェクトに宛先をセットします。 239 * セットする際に、アカウントエラーとなっているアドレスを除外します。 240 * 宛先が存在しない場合、例外を投げます。 241 * 242 * 計算方法は親クラスのprivateメソッドを流用。 243 * 値はクラス変数のリストに格納するように変更しています。 244 * 245 * @param invalidAddr 宛先のリスト 246 */ 247 private void setMailDst( final List<String> invalidAddr ){ 248 249 Map<Integer, ArrayList<String>> tempMap = new HashMap<Integer, ArrayList<String>>(); 250 tempMap.put( Integer.valueOf( MailPattern.KBN_TO ), toList ); 251 tempMap.put( Integer.valueOf( MailPattern.KBN_CC ), ccList ); 252 tempMap.put( Integer.valueOf( MailPattern.KBN_BCC ), bccList ); 253 254 Map tmp = getMailDstMap(); 255 for( String dstId : getMailDstMap().keySet()) { 256 String[] dstInfo = getMailDstMap().get( dstId ); 257 Integer kbn = Integer.valueOf( dstInfo[MailPattern.IDX_DST_KBN] ); 258 if( !invalidAddr.contains( dstInfo[MailPattern.IDX_DST_ADDR] ) 259 && !FGJ_ADDR_ERR.equals( dstInfo[MailPattern.IDX_FGJ] )){ 260 dstInfo[MailPattern.IDX_FGJ] = FGJ_SEND_OVER; 261 262 String name = dstInfo[MailPattern.IDX_DST_NAME]; 263 if( name != null && name.length() > 0 ) { 264 tempMap.get( kbn ).add( dstInfo[MailPattern.IDX_DST_NAME] + "<"+ dstInfo[MailPattern.IDX_DST_ADDR] + ">" ); 265 } 266 else { 267 tempMap.get( kbn ).add( dstInfo[MailPattern.IDX_DST_ADDR] ); 268 } 269 } 270 else { 271 if( FGJ_SEND_OVER.equals( dstInfo[MailPattern.IDX_FGJ] ) ) { 272 dstInfo[MailPattern.IDX_FGJ] = FGJ_ACNT_ERR; 273 } 274 } 275 } 276 277 // 宛先が全部無効の場合、例外を投げます 278 if( toList.isEmpty() && ccList.isEmpty() && bccList.isEmpty()){ 279 String errMsg = "宛先のメールアドレスが有効ではありません。" 280 + "TO , CC , BCC のいづれにもアドレスが設定されていません。"; 281 throw new RuntimeException( errMsg ); 282 } 283 } 284 285 /** 286 * エラーテーブルにエラーメッセージを登録します。 287 * 親のprivateメソッドを流用。エラーメールの送信は行いません。 288 * 289 * @param paraKey パラメータキー(GE36.PARA_KEY) 290 * @param systemId システムID 291 * @param emList エラーメッセージリスト 292 * 293 */ 294 private void writeErrorTable( final String paraKey, final String systemId, final List<String> emList ){ 295 String[] insGE36Args = new String[6]; 296 insGE36Args[GE36_PARA_KEY] = paraKey; 297 insGE36Args[GE36_DYSET] = HybsSystem.getDate( "yyyyMMddHHmmss" ); 298 insGE36Args[GE36_USRSET] = "DAEMON"; 299 insGE36Args[GE36_PGUPD] = "DAEMON"; 300 insGE36Args[GE36_SYSTEM_ID] = systemId; 301 for( int i=0; i< emList.size(); i++ ){ 302 insGE36Args[GE36_ERRMSG] = trim( emList.get( i ), 4000); 303 DBUtil.dbExecute( insGE36, insGE36Args, appInfo, DBID ); 304 } 305 } 306}