    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 * ②SOAPメッセージの生成機能
    037 *  SOAPメッセージは、以下のようなXML構造で定義されます。
    038 *
    039 *  &lt;env:Envelope env:xmlns="..." xsi:xmlns="..."&gt;
    040 *    &lt;env:Body&gt;
    041 *       &lt;tns:[methodName] xmlns:tns="[nameSpace]"&gt;
    042 *          [methodParameter(XML)]
    043 *        &lt;/tns:[methodName]&gt;
    044 *    &lt;/env:Body&gt;
    045 *  &lt;/env:Envelope&gt;
    046 *
    047 *  SOAPConnectクラスでは、[methodParameter(メソッドパラメーター)]の定義方法に2種類の方法があります。
    048 *
    049 *  (a)keys,valsによる指定
    050 *   keys,valsを指定することで、これらを内部的にXMLデータに変換し、パラメーター部分のXML
    051 *   情報を生成します。
    052 *
    053 *   例)
    054 *     keys="param0&gt;AAA,param0&gt;BBB,param1&gt;CCC,DDD"
    055 *     vals="v1,v2,v3,v4"
    056 *    [変換結果]
    057 *       &lt;param0&gt;
    058 *         &lt;AAA&gt;v1&lt;/AAA&gt;
    059 *         &lt;BBB&gt;v2&lt;/BBB&gt;
    060 *       &lt;/param0&gt;
    061 *       &lt;param1&gt;
    062 *         &lt;CCC&gt;v3&lt;/CCC&gt;
    063 *       &lt;/param1&gt;
    064 *       &lt;DDD&gt;v4&lt;/DDD&gt;
    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                ＵＲＬを指定します。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                ＵＲＬを指定します。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                ＵＲＬを指定します。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}