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}