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.util; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.PrintWriter; 021import java.net.URLConnection; 022 023/** 024 * SOAPConnect は、URLConnectクラスの拡張版で、SOAP接続を行うための機能を追加しています。 025 * 基本的な機能は、{@link org.opengion.fukurou.util.URLConnect}を参照して下さい。 026 * 027 * SOAP対応の追加機能としては、以下の2点があります。 028 * <pre> 029 * @ヘッダー情報の変更 030 * SOAP接続では、通常のURLConnectに加えて、以下のヘッダー情報を付加しています。 031 * 032 * 1.Content = text/xml;charset=UTF-8" 033 * 2.Accept = text/xml, multipart/related, text/html, image/gif, image/jpeg, 034 * 3.Soapaction = "[NameSpace][SOAPMethodName]" 035 * 036 * ASOAPメッセージの生成機能 037 * SOAPメッセージは、以下のようなXML構造で定義されます。 038 * 039 * <env:Envelope env:xmlns="..." xsi:xmlns="..."> 040 * <env:Body> 041 * <tns:[methodName] xmlns:tns="[nameSpace]"> 042 * [methodParameter(XML)] 043 * </tns:[methodName]> 044 * </env:Body> 045 * </env:Envelope> 046 * 047 * SOAPConnectクラスでは、[methodParameter(メソッドパラメーター)]の定義方法に2種類の方法があります。 048 * 049 * (a)keys,valsによる指定 050 * keys,valsを指定することで、これらを内部的にXMLデータに変換し、パラメーター部分のXML 051 * 情報を生成します。 052 * 053 * 例) 054 * keys="param0>AAA,param0>BBB,param1>CCC,DDD" 055 * vals="v1,v2,v3,v4" 056 * [変換結果] 057 * <param0> 058 * <AAA>v1</AAA> 059 * <BBB>v2</BBB> 060 * </param0> 061 * <param1> 062 * <CCC>v3</CCC> 063 * </param1> 064 * <DDD>v4</DDD> 065 * 066 * この定義方法では、項目の値を"null"とすることで、XMLで言うところの 067 * 「xsi:nil="true"」のデータを表現することもできます。 068 * 069 * また、キー名の先頭を'@'にすることで、項目名に名前空間のPREFIXを付加することができます。 070 * 一般的には、JavaやRubyで実装されたWebサービスを呼び出しする場合は、必要ありませんが、 071 * .NETで実装されたWebサービスを呼び出しする場合は、各項目にPREFIXを付与しないと、正しく 072 * パラメーターを渡すことができません。 073 * 074 * ※現時点では、keysの階層定義は、2階層まで対応しています。 075 * 3階層以上のXML構造を定義する場合は、postFile属性によるファイル指定又は、Body部分で直接 076 * XMLデータを記述して下さい。 077 * 078 * (b)XMLデータを直接指定 079 * メソッドパラメーターの部分のXMLデータを直接指定します。 080 * この場合、(a)のように、xsi:nil="true"や、パラメーターキーへのPREFIXの付加は、予め行って 081 * おく必要があります。 082 * なお、パラメーターキーのPREFIXは、"tns:"です。 083 * 084 * Usage: java org.opengion.fukurou.util.SOAPConnect [-info/-data] … url [user:passwd] 085 * 086 * args[*] : [-info/-data] 情報の取得か、データの取得かを指定します(初期値:-data)。 087 * args[*] : [-data=ファイル名] メソッドのパラメーターが記述されたXMLファイルを指定します。 088 * args[*] : [-out=ファイル名] 結果をファイルに出力します。ファイルエンコードも指定します。 089 * args[*] : [-nameSpace=名前空間] メソッド名及びパラメータ 090 * args[*] : [-keys=キー一覧] メソッドのパラメーターのキー一覧を指定します。(dataを指定した場合は無視されます) 091 * args[*] : [-vals=値一覧] メソッドのパラメーターの値一覧を指定します。 (dataを指定した場合は無視されます) 092 * args[A] : url URLを指定します。GETの場合、パラメータは ?KEY=VALです。 093 * args[B] : [user:passwd] BASIC認証のエリアへのアクセス時に指定します。 094 * </pre> 095 * 096 * @version 4.0 097 * @author Hiroki Nakamura 098 * @since JDK5.0, 099 */ 100public class SOAPConnect extends URLConnect { 101 102 private static final String CONTENT_TYPE = "text/xml;charset=UTF-8"; 103 private static final String ACCEPT = "text/xml, multipart/related, text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"; 104 105 final private String nameSpace; 106 final private String methodName; 107 108 /** 109 * コンストラクター 110 * 111 * ここでは、送信するXMLデータをキー及び値の一覧から生成するためのコンストラクターを定義しています。 112 * 113 * @param url 接続するアドレスを指定します。(http://server:port/dir/file.html) 114 * @param pass ユーザー:パスワード(認証接続が必要な場合) 115 * @param ns 名前空間を指定します。 116 * @param method メソッド名を指定します。 117 * @param ks 送信するメソッドパラメーターのキー一覧を指定します。 118 * @param vs 送信するパラメーターの値一覧を指定します。 119 */ 120 public SOAPConnect( final String url, final String pass, final String ns, final String method, final String[] ks, final String[] vs ) { 121 this( url, pass, ns, method, makeParamData( ks, vs ) ); 122 } 123 124 /** 125 * コンストラクター 126 * 127 * ここでは、送信するXMLデータを直接指定するためのコンストラクターを定義しています。 128 * 129 * @param url 接続するアドレスを指定します。(http://server:port/dir/file.html) 130 * @param pass ユーザー:パスワード(認証接続が必要な場合) 131 * @param ns 名前空間を指定します。 132 * @param method メソッド名を指定します。 133 * @param xmlData パラメーターのXMLデータ 134 */ 135 public SOAPConnect( final String url, final String pass, final String ns, final String method, final String xmlData ) { 136 super( url, pass ); 137 nameSpace = ns; 138 methodName = method; 139 setPostData( getSoapEnvelop( xmlData ) ); 140 } 141 142 /** 143 * URL と ユーザー:パスワードを与えて、URLConnectionを返します。 144 * 145 * SOAP接続では、通常のURLConnectに加えて、以下のヘッダー情報を付加しています。 146 * 147 * 1.Content = text/xml;charset=UTF-8" 148 * 2.Accept = text/xml, multipart/related, text/html, image/gif, image/jpeg, 149 * 3.Soapaction = "[NameSpace][SOAPMethodName]" 150 * 151 * @return URLConnectionオブジェクト 152 * @throws IOException 入出力エラーが発生したとき 153 * @see URLConnect#getConnection() 154 */ 155 @Override 156 protected URLConnection getConnection() throws IOException { 157 URLConnection urlConn = super.getConnection(); 158 urlConn.setRequestProperty( "Content-Type", CONTENT_TYPE ); 159 urlConn.setRequestProperty( "Accept", ACCEPT ); 160 urlConn.setRequestProperty( "Soapaction", "\"" + nameSpace + methodName + "\"" ); 161 return urlConn; 162 } 163 164 /** 165 * メソッドパラメーターのXMLデータに、Envelop情報を付加し、SOAP通信を行うための 166 * 完全なXML情報を生成します。 167 * 168 * @param data メソッドのパラメーターとなるXMLデータ 169 * 170 * @return SOAPで送信される全体のXML情報 171 */ 172 private String getSoapEnvelop( final String data ) { 173 StringBuilder buf = new StringBuilder(); 174 175 buf.append( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" ); 176 buf.append( "<env:Envelope " ); 177 buf.append( " xmlns:env=\"http://schemas.xmlsoap.org/soap/envelope/\" " ); 178 buf.append( " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><env:Body>" ); 179 buf.append( "<tns:" ).append( methodName ).append( " xmlns:tns=\"" ).append( nameSpace ).append( "\">" ); 180 buf.append( data ); 181 buf.append( "</tns:" ).append( methodName ).append( ">" ); 182 buf.append( "</env:Body></env:Envelope>" ); 183 184 return buf.toString(); 185 } 186 187 /** 188 * キー一覧と値一覧から、メソッドパラメータのXMLデータを生成します。 189 * 190 * @param keys キー一覧 191 * @param vals 値一覧 192 * 193 * @return メソッドパラメーターのXMLデータ 194 */ 195 private static final String makeParamData( final String[] keys, final String[] vals ) { 196 StringBuilder buf = new StringBuilder(); 197 198 String preParentKey = null; 199 String key = null; 200 for( int i = 0; i < keys.length; i++ ) { 201 String[] keyTags = StringUtil.csv2Array( keys[i], '>' ); 202 203 if( keyTags.length > 2 ) { 204 String errMsg = "keys,vals形式では2階層までのデータのみを定義することができます。" 205 + "3階層以上のデータに関しては、直接XMLでメソッドパラメーターを指定して下さい。"; 206 throw new RuntimeException( errMsg ); 207 } 208 209 // 親の終了タグを出力 210 if( preParentKey != null 211 && ( keyTags.length == 1 || !preParentKey.equals( keyTags[0] ) ) ) { 212 buf.append( getKeyTag( preParentKey, true ) ); 213 } 214 215 if( keyTags.length > 1 ) { 216 // 親の開始タグを出力 217 if( preParentKey == null || !preParentKey.equals( keyTags[0] ) ) { 218 buf.append( getKeyTag( keyTags[0], false ) ); 219 } 220 key = keyTags[1]; 221 preParentKey = keyTags[0]; 222 } 223 else { 224 key = keyTags[0]; 225 preParentKey = null; 226 } 227 228 // 値の出力 229 if( "null".equals( vals[i] ) ) { 230 buf.append( getKeyTag( key, false, "xsi:nil=\"true\"" ) ); 231 } 232 else { 233 buf.append( getKeyTag( key, false ) ).append( vals[i] ); 234 } 235 buf.append( getKeyTag( key, true ) ); 236 } 237 238 // 親の終了タグの出力 239 if( preParentKey != null ) { 240 buf.append( getKeyTag( preParentKey, true ) ); 241 } 242 243 return buf.toString(); 244 } 245 246 /** 247 * タグキーからタグ文字列を生成します。 248 * 249 * @param key タグキー 250 * @param isEnd 終了タグ or 開始タグ 251 * 252 * @return タグ文字列 253 */ 254 private static final String getKeyTag( final String key, final boolean isEnd ) { 255 return getKeyTag( key, isEnd, null ); 256 } 257 258 /** 259 * タグキーからタグ文字列を生成します。 260 * 261 * @param key タグキー 262 * @param isEnd 終了タグ or 開始タグ 263 * @param attr 属性値 264 * 265 * @return タグ文字列 266 */ 267 private static final String getKeyTag( final String key, final boolean isEnd, final String attr ) { 268 StringBuilder buf = new StringBuilder(); 269 buf.append( '<' ); 270 if( isEnd ) { buf.append( '/' ); } 271 272 boolean isQualified = ( key.charAt( 0 ) == '@' ); 273 String tmp = ( isQualified ? key.substring( 1 ) : key ); 274 if( isQualified ) { 275 buf.append( "tns:" ); 276 } 277 buf.append( tmp ); 278 if( attr != null && attr.length() > 0 ) { 279 buf.append( " " ).append( attr ); 280 } 281 buf.append( '>' ); 282 return buf.toString(); 283 } 284 285 /** 286 * サンプル実行用のメインメソッド 287 * 288 * Usage: java org.opengion.fukurou.util.SOAPConnect [-info/-data] … url [user:passwd] 289 * 290 * args[*] : [-info/-data] 情報の取得か、データの取得かを指定します(初期値:-data)。 291 * args[*] : [-data=ファイル名] 送信するデータが記述されたXMLファイルを指定します。 292 * args[*] : [-out=ファイル名] 結果をファイルに出力します。ファイルエンコードも指定します。 293 * args[*] : [-nameSpace=名前空間] メソッド名及びパラメーターの名前空間を指定します。 294 * args[*] : [-methodName=メソッド名] メソッド名を指定します。 295 * args[*] : [-keys=キー一覧] メソッドのパラメーターのキー一覧を指定します。(dataを指定した場合は無視されます) 296 * args[*] : [-vals=値一覧] メソッドのパラメーターの値一覧を指定します。 (dataを指定した場合は無視されます) 297 * args[A] : url URLを指定します。GETの場合、パラメータは ?KEY=VALです。 298 * args[B] : [user:passwd] BASIC認証のエリアへのアクセス時に指定します。 299 * 300 * @param args コマンド引数配列 301 * @throws IOException 入出力エラーが発生した場合 302 */ 303 public static void main( final String[] args ) throws IOException { 304 if( args.length < 3 ) { 305 LogWriter.log( "Usage: java org.opengion.fukurou.util.SOAPConnect [-info/-data] … url [user:passwd]" ); 306 LogWriter.log( " args[*] : [-info/-data] 情報の取得か、データの取得かを指定します(初期値:-data)。" ); 307 LogWriter.log( " args[*] : [-data=ファイル名] 送信するデータが記述されたXMLファイルを指定します。" ); 308 LogWriter.log( " args[*] : [-out=ファイル名] 結果をファイルに出力します。ファイルエンコードも指定します。" ); 309 LogWriter.log( " args[*] : [-nameSpace=名前空間] メソッド名及びパラメーターの名前空間を指定します。" ); 310 LogWriter.log( " args[*] : [-methodName=メソッド名] メソッド名を指定します。" ); 311 LogWriter.log( " args[*] : [-keys=キー一覧] メソッドのパラメーターのキー一覧を指定します。(dataを指定した場合は無視されます)" ); 312 LogWriter.log( " args[*] : [-vals=値一覧] メソッドのパラメーターの値一覧を指定します。 (dataを指定した場合は無視されます)" ); 313 LogWriter.log( " args[A] : url URLを指定します。GETの場合、パラメータは ?KEY=VALです。" ); 314 LogWriter.log( " args[B] : [user:passwd] BASIC認証のエリアへのアクセス時に指定します。" ); 315 return; 316 } 317 318 boolean isInfo = false ; 319 String postFile = null ; 320 String outFile = null ; 321 String ns = null; 322 String method = null; 323 String[] ks = null; 324 String[] vs = null; 325 String[] vals = new String[2]; // url,userPass の順に引数設定 326 327 int adrs = 0; 328 for( int i=0; i<args.length; i++ ) { 329 String arg = args[i]; 330 if( arg.equalsIgnoreCase( "-info" ) ) { 331 isInfo = true; 332 } 333 else if( arg.equalsIgnoreCase( "-data" ) ) { 334 isInfo = false; 335 } 336 else if( arg.startsWith( "-post=" ) ) { 337 postFile = arg.substring( 6 ); 338 } 339 else if( arg.startsWith( "-out=" ) ) { 340 outFile = arg.substring( 5 ); 341 } 342 else if( arg.startsWith( "-nameSpace=" ) ) { 343 ns = arg.substring( 11 ); 344 } 345 else if( arg.startsWith( "-methodName=" ) ) { 346 method = arg.substring( 12 ); 347 } 348 else if( arg.startsWith( "-keys=" ) ) { 349 ks = StringUtil.csv2Array( arg.substring( 6 ) ); 350 } 351 else if( arg.startsWith( "-vals=" ) ) { 352 vs = StringUtil.csv2Array( arg.substring( 6 ) ); 353 } 354 else if( arg.startsWith( "-" ) ) { 355 System.out.println( "Error Argment:" + arg ); 356 } 357 else { 358 vals[adrs++] = arg; 359 } 360 } 361 362 String urlStr = vals[0] ; 363 String userPass = vals[1] ; 364 365 // POST データは、connect() する前に、設定します。 366 URLConnect conn = null; 367 if( postFile != null ) { 368 FileString file = new FileString(); 369 file.setFilename( postFile ); 370 String postData = file.getValue(); 371 conn = new SOAPConnect( urlStr,userPass, ns, method, postData ); 372 } 373 else { 374 conn = new SOAPConnect( urlStr,userPass, ns, method, ks, vs ); 375 } 376 377 conn.connect(); 378 379 final PrintWriter writer ; 380 if( outFile != null ) { 381 writer = FileUtil.getPrintWriter( new File( outFile ),"UTF-8" ); 382 } 383 else { 384 writer = FileUtil.getLogWriter( "System.out" ); 385 } 386 387 if( isInfo ) { 388 writer.println( "URL :" + conn.getUrl() ); 389 writer.println( "Type :" + conn.getType() ); 390 writer.println( "Code :" + conn.getCode() ); 391 writer.println( "Message:" + conn.getMessage() ); 392 writer.println( "Charset:" + conn.getCharset() ); 393 } 394 else { 395 writer.println( conn.readData() ); 396 } 397 398 conn.disconnect(); 399 400 Closer.ioClose( writer ); 401 } 402}