TypeMapping

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

TypeMapping

Koichi Kobayashi
はじめまして,小林 (koichik) と申します.

Axis 1.4 で,TypeMappingImpl の doAutoTypes を true に
設定した場合に Map が適切にエンコードされないという現象に
遭遇しています.
以下,長文になりますが状況を詳しく説明します.


TypaMapping は TypeMappingDelegate を使ってチェーン状に
構成することができますが,仮に二つの TypeMappingDlegate が
以下のようにあるとして,

                        next
TypeMappingDelegate(1) -----> TypeMappingDelegate(2)
  └TypeMappingImpl(1)          └TypeMappingImpl(2)

TypeMappingImpl(1) はほとんど空ですが,Map を扱うために
MapSerializer/MapDeserializer を登録しています.
TypeMappingImpl(2) には標準のマッピングが登録されています.
# この TypeMapping チェーンは WSDDService#initTMR() で
# 作成されるものです.

そして,TypeMappingImpl(1) の doAutoTypes をtrue に
設定して利用しています.

このような構成で戻り値型が Map のメソッドを Java:RPC で
呼び出すと,Map のキーや値 (String) が xsd:base64Binary で
エンコードされてしまいます.

Axis の動きを追いかけてみたところ,まずは TypeMappingImpl(1) に
登録された MapSerializer がキーや値をシリアライズするために
SerializationContext#serialize() を呼び出します.

MapSerializer.java(96)
================================================================================
            context.serialize(QNAME_KEY,   null, key, null, null, Boolean.TRUE);
            context.serialize(QNAME_VALUE, null, val, null, null, Boolean.TRUE);
================================================================================

ここではシリアライズ対象のオブジェクト (実際は String) である
key と val を渡していますが,第 4 引数の xmlType (QName) には
null を渡しています.

呼び出された SerializationContext はいくつかのメソッドを経て
TypeMappingDelegate(1) の getSerializer() を呼び出します.
TypeMappingDelegate(1) はそのまま TypeMappingImpl(1) に委譲します.
この時点でも xmlType は null のままです.

TypeMappingImpl.java(286〜294)
================================================================================
    public javax.xml.rpc.encoding.SerializerFactory
        getSerializer(Class javaType, QName xmlType)
        throws JAXRPCException {

        javax.xml.rpc.encoding.SerializerFactory sf = null;

        // If the xmlType was not provided, get one
        if (xmlType == null) {
            xmlType = getTypeQName(javaType, null);
================================================================================

xmlType が null で呼び出されるため,getTypeQName() を呼び出します.
この時の第 2 引数 TypeMappingDelegate は null です.

getTypeQName() は XML 型を解決しようとしますが,TypeMappingImpl(1) は
String に対するマッピングを持っていません.
TypeMappingImpl(2) は String のマッピングを持っていますが,
getTypeQName() の第 2 引数で渡された next が null であるため,
チェーンの後続から解決することもできません.

そして TypeMappingImpl(1) は doAutoTypes が true に設定されて
いるため,String に対して BeanSerializer/BeanDesrializer を
登録してしまいます.

TypeMappingImpl.java(674〜689)
================================================================================
        if (xmlType == null && shouldDoAutoTypes())
        {
            xmlType = new QName(
                Namespaces.makeNamespace( javaType.getName() ),
                Types.getLocalNameFromFullName( javaType.getName() ) );

            /* If doAutoTypes is set, register a new type mapping for the
            * java class with the above QName.  This way, when getSerializer()
            * and getDeserializer() are called, this QName is returned and
            * these methods do not need to worry about creating a serializer.
            */
            internalRegister( javaType,
                              xmlType,
                              new BeanSerializerFactory(javaType, xmlType),
                              new BeanDeserializerFactory(javaType, xmlType) );
        }
================================================================================

この結果,String であるマップのキーや値が BeanSerializer により
xsd:base64Binary でエンコードされてしまうようです.


ここまで調査したのですが,問題が Axis の実装にあるのか,
上で説明した使い方にあるのかが分かりません.

TypeMappingDelegate(1) ではなく TypeMappingDelegate(2) の
doAutoTypes を true にすべきなのかと思い試してみましたが,
その場合 Map は適切に扱えるようになったものの,今度は
Bean の配列に対して TypeMappingImpl(1) が Object 配列の
Serializer を返してしまうなど,doAutoTypes による
BeanSerializer/BeanDeserializer の自動登録が適切に働かなく
なってしまいました.


ここで (ようやく) 質問なのですが,doAutoTypes を有効にした上で,
Map など個別に登録した Serializr/Deserializer も適切に扱うには
どのようにすべきなのでしょうか?


正しいやり方が分からないので,現在はとりあえず次のように
回避しています.

xmlType が null のまま TypeMappingDelegate(1) が呼ばれると
TypeMappingImpl(2) に登録されているマッピングが使われないので,
TypeMappingDelegage(1) の前に TypeMappingDelegate サブクラスを
TypeMappingDelegate(0) として登録しました.
TypeMappingDelegate サブクラスでは getSerializer() をオーバーライドし,
TypeMappingImpl に委譲する前に xmlType を解決します.

================================================================================
    public SerializerFactory getSerializer(Class javaType, QName xmlType) throws JAXRPCException {
        if (xmlType == null) {
            xmlType = getTypeQName(javaType);
        }
        return super.getSerializer(javaType, xmlType);
================================================================================

これにより,TypeMappingImpl(2) に登録されている型が
適切にシリアライズされるようになりました.
doAutoTypes による BeanSerializer/BeanDeserializer の
自動登録も問題ありません.

ただし,TypeMappingDelegate のコンストラクタは可視性が
指定されていないため (package スコープ),やむを得ず
勝手に org.apache.axis.encoding パッケージを使っています.

もし TypeMappingDelegate のサブクラスを作成するやり方が
適切なのであれば,コンストラクタを public にして頂けると
ありがたいです.


以上,かなりの長文になってしまいましたが,doAutoTypes を有効にして
Map も扱うための適切な方法があればご教示頂けないでしょうか.
よろしくお願いします.


--
<component name="koichik">
    <property name="fullName">"Koichi Kobayashi"</property>
    <property name="email">"[hidden email]"</property>
    <property name="blog">"http://d.hatena.ne.jp/koichik"</property>
</component>



---------------------------------------------------------------------
To unsubscribe, e-mail: [hidden email]
For additional commands, e-mail: [hidden email]