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 * A@で呼び出しされたサーブレットが、読取処理を実行するためのオブジェクトを生成し、
044 *   読取処理を実行する。
045 * B読取した結果XMLをパースし、その読取データを伝送オブジェクトに渡し伝送処理を実行する。
046 * C伝送処理実行後、Bの結果XMLから得られる更新キー(配列)をPostデータとして渡し、
047 *   HTTP経由で後処理(完了処理またはエラー処理)を実行する。
048 *
049 * まず@について、呼び出しされるサーブレットは、
050 *   [リモート接続先URL]servlet/remoteControl?class=TransferReadWrapper&type=read になります。
051 *   [リモート接続先URL]は、http://[ホスト名]:[ポート番号]/[コンテキスト名]の形式になりますが、
052 *   これについては、読取対象で指定します。
053 *   接続時にエラーが発生した場合や、レスポンスデータに対して"row_error"という
054 *   文字列が存在する場合は、エラーとして処理します。
055 *   それ以外の場合は、正常終了として処理します。
056 * 次にAについて、サーブレット経由で行われる伝送処理について、その読取方法は、
057 *   このクラスを継承した各サブクラスのクラス名により決定されます。
058 *   具体的には、サブクラスのクラス名に対して、このクラス(親クラス)のクラス名+"_" を除外した部分が
059 *   読取方法として認識されます。
060 *   例として、サブクラス名がTransferRead_HTTP_CB01の場合、接続先における読取方法は
061 *   旧伝送DB読取(CB01)となります。
062 *   また、リモートホスト上で実行される伝送処理の[リモート読取対象]は、元の読取対象で設定します。
063 *   このことから、HTTP経由で読取処理を行う場合、元の読取対象には、[リモート接続先URL]と
064 *   [リモート読取対象]の2つを設定する必要があります。
065 *   具体的な設定方法については各サブクラスのJavaDocを参照して下さい。
066 * Bについて、返されるXMLデータは[レスポンスデータのXML構造]の形式になります。
067 *   ここで、dataListは、伝送実行オブジェクトに渡されるデータになります。
068 *   また、keyListについては、伝送実行終了後、HTTP経由で完了処理を行うための更新キーになります。
069 *   (ローカルの伝送読取オブジェクトはトランザクションに対して同一オブジェクトになりますが、
070 *    HTTP接続先でサーブレット経由で生成される、リモートの伝送読取オブジェクトは、
071 *    読取処理と完了/エラー処理で異なります。このため、読取したデータのキーをローカルに保持し、
072 *    最後の完了/エラー処理の際に、読取したデータのキー(更新キー)をPostDataとして渡すことで、
073 *    正しく完了/エラー処理が行われるように対応しています)
074 * 最後にCについて、呼び出しされるサーブレットは、
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                        if( nd.getNodeType() == Node.ELEMENT_NODE ) {
173                                if( "data".equals( ((Element)nd).getTagName() ) )       {
174                                        valList.add( nd.getTextContent() );
175                                }
176                        }
177                }
178
179                // 以降の処理でデータを更新するためのキーを取得します。
180                NodeList keyChilds = root.getElementsByTagName( "keyList" ).item(0).getChildNodes();
181                int numKeyChild = keyChilds.getLength();
182                for( int i=0; i<numKeyChild; i++ ) {
183                        Node nd = keyChilds.item(i);
184                        if( nd.getNodeType() == Node.ELEMENT_NODE ) {
185                                if( "key".equals( ((Element)nd).getTagName() ) )        {
186                                        keyList.add( nd.getTextContent() );
187                                }
188                        }
189                }
190//              keys = keyList.toArray( new String[0] );
191                keys = keyList.toArray( new String[keyList.size()] );
192
193//              return valList.toArray( new String[0] );
194                return valList.toArray( new String[valList.size()] );
195        }
196
197        /**
198         * 読取したデータに対して完了処理を行います。
199         * 接続パラメータには、"type=complete"が付加されます。
200         *
201         * @param config 伝送設定オブジェクト
202         * @param tran トランザクションオブジェクト
203         */
204        @Override
205        public void complete( final TransferConfig config, final Transaction tran ) {
206                splitReadObj( config.getReadObj() );
207                String url = getRemoteHost() + REMOTE_SERVLET + "&type=complete";
208                String postData = getPostData( keys, config );
209                URLConnect conn = null;
210                try {
211                        conn = connect( url, postData, config );
212                }
213                catch( IOException ex ) {
214                        String errMsg = "URL接続時に例外が発生しました。[URL=" + url + "]";
215                        throw new RuntimeException( errMsg, ex );
216                }
217                finally {
218                        if( conn != null ) { conn.disconnect(); }
219                }
220        }
221
222        /**
223         * 読取したデータに対してエラー処理を行います。
224         * 接続パラメータには、"type=error"が付加されます。
225         *
226         * @param config 伝送設定オブジェクト
227         * @param appInfo DB接続情報
228         */
229        @Override
230        public void error( final TransferConfig config, final ApplicationInfo appInfo ) {
231                splitReadObj( config.getReadObj() );
232                String url = getRemoteHost() + REMOTE_SERVLET + "&type=error";
233                String postData = getPostData( keys, config );
234                URLConnect conn = null;
235                try {
236                        conn = connect( url, postData, config );
237                }
238                catch( IOException ex ) {
239                        String errMsg = "URL接続時に例外が発生しました。[URL=" + url + "]";
240                        throw new RuntimeException( errMsg, ex );
241                }
242                finally {
243                        if( conn != null ) { conn.disconnect(); }
244                }
245        }
246
247        /**
248         * (このクラスでは、サポートされてません。)
249         *
250         * @return 更新キー(配列)
251         */
252        @Override
253        public String[] getKeys() {
254                String errMsg = "このクラスでは、サポートされてません。";
255                throw new RuntimeException( errMsg );
256        }
257
258        /**
259         * (このクラスでは、サポートされてません。)
260         *
261         * @param keys 更新キー(配列)
262         */
263        @Override
264        public void setKeys( final String[] keys ) {
265                String errMsg = "このクラスでは、サポートされてません。";
266                throw new RuntimeException( errMsg );
267        }
268
269        /**
270         * ローカルの読取対象を、リモート接続先の読取対象とリモート接続先URLに分解します。
271         *
272         * @param localReadObj ローカルの読取対象
273         */
274        protected abstract void splitReadObj( final String localReadObj );
275
276        /**
277         * リモート接続先URLを返します。
278         * このメソッドは、{@link #splitReadObj(String)}の後に呼び出しする必要があります。
279         *
280         * @return リモート接続先URL
281         */
282        protected abstract String getRemoteHost();
283
284        /**
285         * リモート接続先の読取対象を返します。
286         * このメソッドは、{@link #splitReadObj(String)}の後に呼び出しする必要があります。
287         *
288         * @return 接続URL
289         */
290        protected abstract String getRemoteReadObj();
291
292        /**
293         * 指定のURLに接続します。
294         *
295         * @param url 接続URL
296         * @param postData POSTするデータ
297         * @param config 伝送設定オブジェクト
298         *
299         * @return 接続オブジェクト
300         * @throws IOException なんらかのエラーが発生した場合。
301         */
302        protected URLConnect connect( final String url, final String postData, final TransferConfig config ) throws IOException {
303                URLConnect conn = new URLConnect( url, TransferConfig.HTTP_AUTH_USER_PASS );
304                if( config.getProxyHost() != null && config.getProxyHost().length() > 0  ) {
305                        conn.setProxy( config.getProxyHost(),config.getProxyPort() );
306                }
307                conn.setCharset( "UTF-8" );
308                conn.setPostData( postData );
309                conn.connect();
310                return conn;
311        }
312
313        /**
314         * 指定のURLに接続しレスポンスデータを返します。
315         *
316         * @param conn URL接続オブジェクト
317         *
318         * @return レスポンスデータ
319         */
320        private String readData( final URLConnect conn ) throws IOException {
321                String readData = conn.readData();
322                // 返されたデータ中に"row_error"が存在する場合はエラーとして処理します。
323                if( readData != null && readData.indexOf( "row_error" ) >= 0 ) {
324                        throw new RuntimeException( readData );
325                }
326                return readData;
327        }
328
329        /**
330         * 伝送設定オブジェクトをURLパラメーターに変換します。
331         *
332         * @param       keys    更新キー(配列)
333         * @param       config  伝送設定オブジェクト
334         *
335         * @return      URLパラメーター
336         */
337        protected String getPostData( final String[] keys, final TransferConfig config ) {
338                // サブクラス名から親クラス名+"_"を除いた部分を読取方法とする。
339                String kbRead = getClass().getName().replace( getClass().getSuperclass().getName() + "_", "" );
340
341                StringBuilder buf = new StringBuilder();
342                buf.append( "KBREAD="           ).append( StringUtil.urlEncode( kbRead ) );
343                buf.append( "&READOBJ="             ).append( StringUtil.urlEncode( getRemoteReadObj() ) );
344                buf.append( "&READPRM="             ).append( StringUtil.urlEncode( config.getReadPrm() ) );
345                buf.append( "&KBEXEC="              ).append( StringUtil.urlEncode( config.getKbExec() ) );
346                buf.append( "&EXECDBID="    ).append( StringUtil.urlEncode( config.getExecDbid() )  );
347                buf.append( "&EXECOBJ="             ).append( StringUtil.urlEncode( config.getExecObj() ) );
348                buf.append( "&EXECPRM="             ).append( StringUtil.urlEncode( config.getExecPrm() ) );
349                buf.append( "&ERROR_SENDTO=").append( StringUtil.urlEncode( config.getErrorSendto() ) );
350                buf.append( "&HFROM="               ).append( StringUtil.urlEncode( config.getHfrom() ) );
351
352                if( keys != null && keys.length > 0 ) {
353                        buf.append( "&n=" ).append( keys.length );
354                        for( int i=0; i<keys.length; i++ ) {
355                                buf.append( "&k" ).append( i ).append( "=" );
356                                buf.append( StringUtil.urlEncode( keys[i] ) );
357                        }
358                }
359                else {
360                        buf.append( "&n=0" );
361                }
362
363                return buf.toString();
364        }
365}