Google+‎ > ‎

Google Http Java Client

公式:google-http-java-client

Google Http Java ClientはGoogleが提供する各種 API用のライブラリー (Google APIs Client Library for Java)
のベースとなるHTTP接続部分や、XML・JSONの解析などをライブラリー化したものです。

Googleのサービスは公式のGoogle APIs Client Library使えばいいので、必要ありませんが
Google以外のXML・JSONサービスと連携する時に使うと便利でしょう。
Apache2.0のオープンソースで公開されています。



XMLを解析する

Google Http Java ClientでのXML変換は、アノテーションを使って簡単なクラスPOJOを作るだけです。
他のObject/XML マッピングは詳しくないので、比較できませんが、記述量が少なくて好感持てます。

例えば、こういうXMLをオブジェクトに変換するために記述が必要なのは
<test>
<item key="k1">hello</item>
<item>value2</item>
</test>

たったの、これだけです。
    public static class Root {
        @Key List<Item> item;
      }

    public static class Item{
        @Key("text()")
        public String text;

        @Key("@key")
        String key="";
    }


XMLをObjectに変換する

マッピングするための、コードは少ないですが、実際の変換までは多少手続きがいります。

import java.io.IOException;
import java.util.List;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.http.xml.XmlHttpParser;
import com.google.api.client.util.Key;
import com.google.api.client.xml.XmlNamespaceDictionary;



public class XmlToObject {

    public static XmlNamespaceDictionary namespaceDictionary = new XmlNamespaceDictionary();
    public static void main(String[] args) {
        namespaceDictionary.set("", "");
       
        ApacheHttpTransport transport=new ApacheHttpTransport();
        HttpRequestFactory factory=createRequestFactory(transport);
        try {
             HttpRequest request=factory.buildGetRequest(new GenericUrl("http://YOUR_HOST/test.xml"));
            HttpResponse response=request.execute();
            System.out.println(response.parseAs(Root.class));
        } catch (IOException e) {
           
            e.printStackTrace();
        }
       
    }
   
    public static class Root {
        @Key String name;
       
        @Key("item") List<Item> items;
       
        @Key("text()") String text;
       
        public String toString(){
            String ret=name+"\n";
            if(items==null){//no items
                return name;
            }
           
           
            for(Item it:items){
                ret+=it+"\n";
            }
            return ret;
        }

       
      }
    public static class Item{
        @Key("text()")
        public String text;
        @Key("@key")
        String key="";
       
        public String toString(){
            return "key="+key+","+text;
        }
    }
   
     public static HttpRequestFactory createRequestFactory(HttpTransport transport) {
            return transport.createRequestFactory(new HttpRequestInitializer() {
                @Override
                public void initialize(HttpRequest request) throws IOException {
                    XmlHttpParser parser=XmlHttpParser.builder(namespaceDictionary).setContentType("text/xml").build();
                    request.addParser(parser);
                }
               
            });
          }
     
}

テスト元のXML
<test>
<item key="k1"></item>
<item>value2</item>
<item/>
<item key="k3">
</item>
<item>hello<dummy/>world</item>
</test>


実行の流れ


Requestを処理するFactoryを作る。
XMLHttpParserを作成する
Requestを処理してResponseを返す
中で、Parseしてもらう。


まずは、ApacheHttpを使ったHttpRequestFactoryを作ります。
ApacheHttpTransport transport=new ApacheHttpTransport();
HttpRequestFactory factory=createRequestFactory(transport);


このFactoryクラスは、HttpRequestInitializerを実装したのを引数で渡して作ります。
少し、ややこしくなるます。おそらくスレッドセーフのために、こういう仕様になっているのだと思います。

さらに、またややこしいのですが、
XmlHttpParser はこういうBuilder形式で作ります。
引数のnamespaceDictionaryですが、別の所で初期化してあります。これは別の所で説明します。
あと、SetでなくAddなのは、text/xmlの処理用のパーサーだからです。

public static HttpRequestFactory createRequestFactory(HttpTransport transport) {
return transport.createRequestFactory(new HttpRequestInitializer() {
@Override
public void initialize(HttpRequest request) throws IOException {
XmlHttpParser parser=XmlHttpParser.builder(namespaceDictionary).setContentType("text/xml").build();
request.addParser(parser);
}

});
}

あとは、リクエストしてマップさせたいクラスを渡して解析するだけです。普通xmlを呼び出すと、
コンテントタイプは、text/xmlで返すので、先程登録したパーサーが処理します。ちなみに、text/plainで返ってくると処理できません。
HttpRequest request=factory.buildGetRequest(new GenericUrl("http://YOUR_HOST/test.xml"));
HttpResponse response=request.execute();
System.out.println(response.parseAs(Root.class));

続けて、マッピングの決まりごとを説明します。

XML/Object マッピングの決まりごと


実行結果
MyTest
key=k1,null
key=,value2
key=,null
key=k3,

key=,world


まずParserに渡すクラスですが、クラス名は何でもいいです。
そして、最初の@Keyというのが マッピングするというアノテーションです。
通常、キーにはマップ先を指定しますが、変数名と同じなら付けなくてもいいです。
@Key String name;と@Key("name") String name; は同じになります。

こう指定すると、ルートのXML中の<name>MyTest</name>を取り出せます。

次の複数の場合は、リストを使います。そしてマップ先のクラスをしていします。
ここだと、itemをマップするので名前で指定しています。
@Key("item") List<Item> items;

そして、属性の値を取る場合は、アノテーションで@Key("@key")という風に@属性名になります。
そして、自分の中のテキストを取り出す場合は、@Key("text()")と、text()というメソッドを使います。

出力でnullになっているのは、値がなくて、セットされてないからです。
String key=""; という風に初期値を入れるのがいいでしょう。

あとテキスト取り出して改行が入っているはそういう仕様です。

さらに、複数のテキストの場合は、text()だと最後のテキストしか取れません。


あと、@Valueと@Nullというアノテーションがあります。
これは、リストのようなEnumの値を処理するのに使うようです。



XML/Object変換時に気を付けたいこと

結構はまるポイントがあります。
XmlHttpParserのデフォルトのコンテンツタイプはapplication/xmlですが、これだと普通のtext/xmlを処理できません。
当然、text/plainで、帰ってきても処理できません。

NameSpace
必ず、指定すること、nullだとこういうエラーになります。
Exception in thread "main" java.lang.NullPointerException
        at com.google.api.client.xml.Xml.parseElementInternal(Xml.java:240)
at com.google.api.client.xml.Xml.parseElement(Xml.java:203)
at InputStreamParser.parse(InputStreamParser.java:64)

さらに、XMLドキュメント側のnamespaceが、アプリ側のnamespaceに登録されていないと
Exception in thread "main" java.lang.NullPointerException
at com.google.api.client.xml.Xml.getFieldName(Xml.java:518)
at com.google.api.client.xml.Xml.parseElementInternal(Xml.java:322)
at com.google.api.client.xml.Xml.parseElement(Xml.java:203)
at InputStreamParser.parse(InputStreamParser.java:64)
at ParseTest2.main(ParseTest2.java:44)

しかも、間違ってnamespace登録すると何もマッピングされません。

読み込むXMLにnamespace指定なければ、アプリ側も空にします。
public static XmlNamespaceDictionary namespaceDictionary = new XmlNamespaceDictionary();
namespaceDictionary.set("", "");

向こうが、<test xmlns="http://www.w3.org/TR/xml-names/"> とかですとアプリ側もおそろいにします。
public static XmlNamespaceDictionary namespaceDictionary = new XmlNamespaceDictionary(); namespaceDictionary.set("", "http://www.w3.org/TR/xml-names/");

ファイルと違って、こちらだけxmlとかにするとうまく動きません。
namespaceDictionary.set("xml", "http://www.w3.org/TR/xml-names/");


ソケットで扱えないローカルのファイルをObjectに変換する

このマッピングはhttp用なので、ファイルとかでは動きません。
でも、ローカルでUnitTestとかする時、不便な気がして(まあJettyとかあるし、テスト時にサーバー起動させればいいだけだけど)いろいろ試しました。
最初、Transportにfileのスキーム追加してと企てましたが、Socket通信でファイルやりとりとか、非現実的なのであきらめました。

結局、パーサーのXmlHttpParserを、XmlInputStreamParserとして、InputStreamを扱えるよう改良しました。


/*
 * Copyright (c) 2011 akjava.com
 * original is com.google.api.client.http.xml.XmlHttpParser;
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

/*
 * Copyright (c) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

import java.io.IOException;
import java.io.InputStream;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import com.google.api.client.util.Types;
import com.google.api.client.xml.Xml;
import com.google.api.client.xml.XmlNamespaceDictionary;


/**
 *  XmlInputStreamParser is XmlHttpParser for File
 * @author aki
 *
 */

public class XmlInputStreamParser {

    private XmlNamespaceDictionary namespaceDictionary;

   

     
      public XmlInputStreamParser(XmlNamespaceDictionary namespaceDictionary) {
      this.namespaceDictionary=namespaceDictionary;
      }

     
      public <T> T parse(InputStream content, Class<T> dataClass) throws IOException {
        try {
          T result = Types.newInstance(dataClass);
          XmlPullParser parser = Xml.createParser();
          parser.setInput(content, null);
          Xml.parseElement(parser, result, namespaceDictionary, null);
          return result;
        } catch (XmlPullParserException e) {
          IOException exception = new IOException();
          exception.initCause(e);
          throw exception;
        } finally {
          content.close();
        }
      }

      public final XmlNamespaceDictionary getNamespaceDictionary() {
        return namespaceDictionary;
      }


   
}




Comments