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.transfer;
017
018import java.io.ByteArrayInputStream;
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.List;
022
023import javax.xml.parsers.DocumentBuilder;
024import javax.xml.parsers.DocumentBuilderFactory;
025
026import org.opengion.fukurou.db.Transaction;
027import org.opengion.fukurou.util.ApplicationInfo;
028import org.opengion.fukurou.util.StringUtil;
029import org.opengion.fukurou.util.URLConnect;
030import org.w3c.dom.Document;
031import org.w3c.dom.Element;
032import org.w3c.dom.Node;
033import org.w3c.dom.NodeList;
034
035/**
036 * 伝送要求に対して、HTTP経由でデータを読取します。
037 *
038 * 読取方法により読み取ったデータをPOSTデータとして送信し、リモートホスト上で
039 * 伝送処理を実行します。
040 *
041 * 処理の流れとしては以下のようになります。
042 * ①HTTPで読取処理を実行するためのサーブレットを呼び出す。
043 * ②①で呼び出しされたサーブレットが、読取処理を実行するためのオブジェクトを生成し、
044 *   読取処理を実行する。
045 * ③読取した結果XMLをパースし、その読取データを伝送オブジェクトに渡し伝送処理を実行する。
046 * ④伝送処理実行後、③の結果XMLから得られる更新キー(配列)をPostデータとして渡し、
047 *   HTTP経由で後処理(完了処理またはエラー処理)を実行する。
048 *
049 * まず①について、呼び出しされるサーブレットは、
050 *   [リモート接続先URL]servlet/remoteControl?class=TransferReadWrapper&type=read になります。
051 *   [リモート接続先URL]は、http://[ホスト名]:[ポート番号]/[コンテキスト名]の形式になりますが、
052 *   これについては、読取対象で指定します。
053 *   接続時にエラーが発生した場合や、レスポンスデータに対して"row_error"という
054 *   文字列が存在する場合は、エラーとして処理します。
055 *   それ以外の場合は、正常終了として処理します。
056 * 次に②について、サーブレット経由で行われる伝送処理について、その読取方法は、
057 *   このクラスを継承した各サブクラスのクラス名により決定されます。
058 *   具体的には、サブクラスのクラス名に対して、このクラス(親クラス)のクラス名+"_" を除外した部分が
059 *   読取方法として認識されます。
060 *   例として、サブクラス名がTransferRead_HTTP_CB01の場合、接続先における読取方法は
061 *   旧伝送DB読取(CB01)となります。
062 *   また、リモートホスト上で実行される伝送処理の[リモート読取対象]は、元の読取対象で設定します。
063 *   このことから、HTTP経由で読取処理を行う場合、元の読取対象には、[リモート接続先URL]と
064 *   [リモート読取対象]の2つを設定する必要があります。
065 *   具体的な設定方法については各サブクラスのJavaDocを参照して下さい。
066 * ③について、返されるXMLデータは[レスポンスデータのXML構造]の形式になります。
067 *   ここで、dataListは、伝送実行オブジェクトに渡されるデータになります。
068 *   また、keyListについては、伝送実行終了後、HTTP経由で完了処理を行うための更新キーになります。
069 *   (ローカルの伝送読取オブジェクトはトランザクションに対して同一オブジェクトになりますが、
070 *    HTTP接続先でサーブレット経由で生成される、リモートの伝送読取オブジェクトは、
071 *    読取処理と完了/エラー処理で異なります。このため、読取したデータのキーをローカルに保持し、
072 *    最後の完了/エラー処理の際に、読取したデータのキー(更新キー)をPostDataとして渡すことで、
073 *    正しく完了/エラー処理が行われるように対応しています)
074 * 最後に④について、呼び出しされるサーブレットは、
075 *   [リモート接続先URL]servlet/remoteControl?class=TransferReadWrapper&type=complete (正常終了時)
076 *   [リモート接続先URL]servlet/remoteControl?class=TransferReadWrapper&type=error    (エラー発生時)
077 *   となります。
078 *
079 * HTTP接続時には、以下のポストデータが送信されます。
080 * [ポストデータ]
081 * ・KBREAD                      (読取方法) ※サブクラスの最後の"_"(アンダーバー)以降の文字列
082 * ・READOBJ             (リモート読取対象) ※ローカルの読取対象からリモート接続先URLを除いた文字列
083 * ・READPRM             (読取パラメーター)
084 * ・KBEXEC                      (実行方法)
085 * ・EXECDBID            (実行接続先DBID)
086 * ・EXECOBJ             (実行対象)
087 * ・EXECPRM             (実行パラメーター)
088 * ・ERROR_SENDTO        (読み取り元ホストコード)
089 * ・HFROM (読み取り元ホストコード)
090 * ・n (キー件数)
091 * ・k1~kn (キー)
092 *
093 * また、データ読取時に返されるXMLは以下の構造をしています。
094 * [レスポンスデータのXML構造]
095 * <root>
096 *  <dataList>
097 *   <data>aaa</data>
098 *   <data>bbb</data>
099 *   <data>ccc</data>
100 *   <data>ddd</data>
101 *   <data>eee</data>
102 *  </dataList>
103 *  <keyList>
104 *   <key>KEY1</key>
105 *   <key>KEY2</key>
106 *   <key>KEY3</key>
107 *  </keyList>
108 * </root>
109 *
110 * @og.group 伝送システム
111 *
112 * @version  5.0
113 * @author   Hiroki.Nakamura
114 * @since    JDK1.6
115 */
116public abstract class TransferRead_HTTP implements TransferRead {
117
118        // リモート制御サーブレット名
119        private static final String REMOTE_SERVLET = "servlet/remoteControl?class=TransferReadWrapper";
120
121        // 更新対象のキー
122        private String[] keys = null;
123
124        /**
125         * URL接続を行いデータを読み取ります。
126         * 接続パラメータには、"type=read"が付加されます。
127         *
128         * @param config 伝送設定オブジェクト
129         * @param tran トランザクションオブジェクト
130         *
131         * @return 読み取りしたデータ(配列)
132         */
133        @Override
134        public String[] read( final TransferConfig config, final Transaction tran ) {
135                splitReadObj( config.getReadObj() );
136                String url = getRemoteHost() + REMOTE_SERVLET+ "&type=read";
137                String postData = getPostData( keys, config );
138                URLConnect conn = null;
139                String data = null;
140                try {
141                        conn = connect( url, postData, config );
142                        data = readData( conn );
143                }
144                catch( IOException ex ) {
145                        String errMsg = "URL接続時に例外が発生しました。[URL=" + url + "]";
146                        throw new RuntimeException( errMsg, ex );
147                }
148                finally {
149                        if( conn != null ) { conn.disconnect(); }
150                }
151
152                List<String> valList = new ArrayList<String>();
153                List<String> keyList = new ArrayList<String>();
154
155                DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
156                Document doc = null;
157                try {
158                        DocumentBuilder builder = dbfactory.newDocumentBuilder();
159                        doc = builder.parse( new ByteArrayInputStream( data.getBytes( "UTF-8" ) ) );
160                }
161                catch( Exception ex ) {
162                        String errMsg = "XMLパース時にエラーが発生しました。";
163                        throw new RuntimeException( errMsg, ex );
164                }
165                Element root = doc.getDocumentElement();
166
167                // データ部分を取得します。
168                NodeList dataChilds = root.getElementsByTagName( "dataList" ).item(0).getChildNodes();
169                int numDataChild = dataChilds.getLength();
170                for( int i=0; i<numDataChild; i++ ) {
171                        Node nd = dataChilds.item(i);
172                        // 6.0.0.1 (2014/04/25) These nested if statements could be combined
173                        if( ( nd.getNodeType() == Node.ELEMENT_NODE ) && "data".equals( ((Element)nd).getTagName() ) ) {
174                                valList.add( nd.getTextContent() );
175                        }
176                }
177
178                // 以降の処理でデータを更新するためのキーを取得します。
179                NodeList keyChilds = root.getElementsByTagName( "keyList" ).item(0).getChildNodes();
180                int numKeyChild = keyChilds.getLength();
181                for( int i=0; i<numKeyChild; i++ ) {
182                        Node nd = keyChilds.item(i);
183                        // 6.0.0.1 (2014/04/25) These nested if statements could be combined
184                        if( nd.getNodeType() == Node.ELEMENT_NODE && "key".equals( ((Element)nd).getTagName() ) ) {
185                                keyList.add( nd.getTextContent() );
186                        }
187                }
188                keys = keyList.toArray( new String[keyList.size()] );
189
190                return valList.toArray( new String[valList.size()] );
191        }
192
193        /**
194         * 読取したデータに対して完了処理を行います。
195         * 接続パラメータには、"type=complete"が付加されます。
196         *
197         * @param config 伝送設定オブジェクト
198         * @param tran トランザクションオブジェクト
199         */
200        @Override
201        public void complete( final TransferConfig config, final Transaction tran ) {
202                splitReadObj( config.getReadObj() );
203                String url = getRemoteHost() + REMOTE_SERVLET + "&type=complete";
204                String postData = getPostData( keys, config );
205                URLConnect conn = null;
206                try {
207                        conn = connect( url, postData, config );
208                }
209                catch( IOException ex ) {
210                        String errMsg = "URL接続時に例外が発生しました。[URL=" + url + "]";
211                        throw new RuntimeException( errMsg, ex );
212                }
213                finally {
214                        if( conn != null ) { conn.disconnect(); }
215                }
216        }
217
218        /**
219         * 読取したデータに対してエラー処理を行います。
220         * 接続パラメータには、"type=error"が付加されます。
221         *
222         * @param config 伝送設定オブジェクト
223         * @param appInfo DB接続情報
224         */
225        @Override
226        public void error( final TransferConfig config, final ApplicationInfo appInfo ) {
227                splitReadObj( config.getReadObj() );
228                String url = getRemoteHost() + REMOTE_SERVLET + "&type=error";
229                String postData = getPostData( keys, config );
230                URLConnect conn = null;
231                try {
232                        conn = connect( url, postData, config );
233                }
234                catch( IOException ex ) {
235                        String errMsg = "URL接続時に例外が発生しました。[URL=" + url + "]";
236                        throw new RuntimeException( errMsg, ex );
237                }
238                finally {
239                        if( conn != null ) { conn.disconnect(); }
240                }
241        }
242
243        /**
244         * (このクラスでは、サポートされてません。)
245         *
246         * @return 更新キー(配列)
247         */
248        @Override
249        public String[] getKeys() {
250                String errMsg = "このクラスでは、サポートされてません。";
251                throw new RuntimeException( errMsg );
252        }
253
254        /**
255         * (このクラスでは、サポートされてません。)
256         *
257         * @param keys 更新キー(配列)
258         */
259        @Override
260        public void setKeys( final String[] keys ) {
261                String errMsg = "このクラスでは、サポートされてません。";
262                throw new RuntimeException( errMsg );
263        }
264
265        /**
266         * ローカルの読取対象を、リモート接続先の読取対象とリモート接続先URLに分解します。
267         *
268         * @param localReadObj ローカルの読取対象
269         */
270        protected abstract void splitReadObj( final String localReadObj );
271
272        /**
273         * リモート接続先URLを返します。
274         * このメソッドは、{@link #splitReadObj(String)}の後に呼び出しする必要があります。
275         *
276         * @return リモート接続先URL
277         */
278        protected abstract String getRemoteHost();
279
280        /**
281         * リモート接続先の読取対象を返します。
282         * このメソッドは、{@link #splitReadObj(String)}の後に呼び出しする必要があります。
283         *
284         * @return 接続URL
285         */
286        protected abstract String getRemoteReadObj();
287
288        /**
289         * 指定のURLに接続します。
290         *
291         * @param url 接続URL
292         * @param postData POSTするデータ
293         * @param config 伝送設定オブジェクト
294         *
295         * @return 接続オブジェクト
296         * @throws IOException なんらかのエラーが発生した場合。
297         */
298        protected URLConnect connect( final String url, final String postData, final TransferConfig config ) throws IOException {
299                URLConnect conn = new URLConnect( url, TransferConfig.HTTP_AUTH_USER_PASS );
300                if( config.getProxyHost() != null && config.getProxyHost().length() > 0  ) {
301                        conn.setProxy( config.getProxyHost(),config.getProxyPort() );
302                }
303                conn.setCharset( "UTF-8" );
304                conn.setPostData( postData );
305                conn.connect();
306                return conn;
307        }
308
309        /**
310         * 指定のURLに接続しレスポンスデータを返します。
311         *
312         * @param conn URL接続オブジェクト
313         *
314         * @return レスポンスデータ
315         */
316        private String readData( final URLConnect conn ) throws IOException {
317                String readData = conn.readData();
318                // 返されたデータ中に"row_error"が存在する場合はエラーとして処理します。
319                if( readData != null && readData.indexOf( "row_error" ) >= 0 ) {
320                        throw new RuntimeException( readData );
321                }
322                return readData;
323        }
324
325        /**
326         * 伝送設定オブジェクトをURLパラメーターに変換します。
327         *
328         * @param       keys    更新キー(配列)
329         * @param       config  伝送設定オブジェクト
330         *
331         * @return      URLパラメーター
332         */
333        protected String getPostData( final String[] keys, final TransferConfig config ) {
334                // サブクラス名から親クラス名+"_"を除いた部分を読取方法とする。
335                String kbRead = getClass().getName().replace( getClass().getSuperclass().getName() + "_", "" );
336
337                StringBuilder buf = new StringBuilder();
338                buf.append( "KBREAD="           ).append( StringUtil.urlEncode( kbRead ) );
339                buf.append( "&READOBJ="         ).append( StringUtil.urlEncode( getRemoteReadObj() ) );
340                buf.append( "&READPRM="         ).append( StringUtil.urlEncode( config.getReadPrm() ) );
341                buf.append( "&KBEXEC="          ).append( StringUtil.urlEncode( config.getKbExec() ) );
342                buf.append( "&EXECDBID="        ).append( StringUtil.urlEncode( config.getExecDbid() )  );
343                buf.append( "&EXECOBJ="         ).append( StringUtil.urlEncode( config.getExecObj() ) );
344                buf.append( "&EXECPRM="         ).append( StringUtil.urlEncode( config.getExecPrm() ) );
345                buf.append( "&ERROR_SENDTO=").append( StringUtil.urlEncode( config.getErrorSendto() ) );
346                buf.append( "&HFROM="           ).append( StringUtil.urlEncode( config.getHfrom() ) );
347
348                if( keys != null && keys.length > 0 ) {
349                        buf.append( "&n=" ).append( keys.length );
350                        for( int i=0; i<keys.length; i++ ) {
351                                buf.append( "&k" ).append( i ).append( "=" );
352                                buf.append( StringUtil.urlEncode( keys[i] ) );
353                        }
354                }
355                else {
356                        buf.append( "&n=0" );
357                }
358
359                return buf.toString();
360        }
361}