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}