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}