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     */
016    package org.opengion.fukurou.transfer;
017    
018    import java.io.ByteArrayInputStream;
019    import java.io.IOException;
020    import java.util.ArrayList;
021    import java.util.List;
022    
023    import javax.xml.parsers.DocumentBuilder;
024    import javax.xml.parsers.DocumentBuilderFactory;
025    
026    import org.opengion.fukurou.db.Transaction;
027    import org.opengion.fukurou.util.ApplicationInfo;
028    import org.opengion.fukurou.util.StringUtil;
029    import org.opengion.fukurou.util.URLConnect;
030    import org.w3c.dom.Document;
031    import org.w3c.dom.Element;
032    import org.w3c.dom.Node;
033    import org.w3c.dom.NodeList;
034    
035    /**
036     * 伝é?要求に対して、HTTP経由でãƒ??タを読取しますã?
037     *
038     * 読取方法により読み取ったデータをPOSTãƒ??タとして送信しã?リモートã?スト上で
039     * 伝é?処ç�?‚’実行しますã?
040     *
041     * 処ç�??流れとしては以下ã?ようになりますã?
042     * â‘?TTPで読取å?ç�?‚’実行するためã?サーブレãƒ?ƒˆを呼び出すã?
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     */
116    public 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    }