Pass a HashMap<String,Object> over SOAP using JAXB

bhcmoney picture bhcmoney · Apr 18, 2013 · Viewed 11.4k times · Source

I am trying to pass a Hashmap over SOAP. I am using CXF wsdl2java to create my schema. And I have created a wrapper class for my HashMap since Hashmap itself cannot be passed over the line.

I have then created adapters to morph that Hashmap into a known type for my wsdl but when my wsdl is created it adds some unneeded abstract map. Below is the code:

Here is my wrapper class for the HashMap

@XmlRootElement(name = "testTO")
public class TestTO {

    private HashMap<String, Object> mapTest;

    public TestTO(){
        this.mapTest = new HashMap<String, Object>();
    }

    @XmlJavaTypeAdapter(MapAdapter.class)
    public HashMap<String, Object> getMapTest() {
        return mapTest;
    }

    public void setMapTest(HashMap<String, Object> mapTest) {
        this.mapTest = mapTest;
    }

}

Here is the MyMap class in which is a known schema type

@XmlJavaTypeAdapter(MapAdapter.class)
public class MyMap extends HashMap<String, Object>{
    public final List<Entry> entryList = new ArrayList<Entry>();
}

This is the Entry Class in which that list contains above:

public class Entry {

    @XmlAttribute
    public String key;

    @XmlElements({
            @XmlElement(name = "byte", type = byte.class),
            @XmlElement(name = "short", type = short.class),
            @XmlElement(name = "int", type = int.class),
            @XmlElement(name = "long", type = long.class),
            @XmlElement(name = "float", type = float.class),
            @XmlElement(name = "double", type = double.class),
            @XmlElement(name = "char", type = char.class),
            @XmlElement(name = "boolean", type = boolean.class),

            @XmlElement(name = "ByteWrapper", type = Byte.class),
            @XmlElement(name = "ShortWrapper", type = Short.class),
            @XmlElement(name = "IntegerWrapper", type = Integer.class),
            @XmlElement(name = "LongWrapper", type = Long.class),
            @XmlElement(name = "FloatWrapper", type = Float.class),
            @XmlElement(name = "DoubleWrapper", type = Double.class),
            @XmlElement(name = "Character", type = Character.class),
            @XmlElement(name = "BooleanWrapper", type = Boolean.class),

            @XmlElement(name = "BigDecimal", type = BigDecimal.class),
            @XmlElement(name = "String", type = String.class),
            @XmlElement(name = "Date", type = Date.class)
    })
    public Object value;

    public Entry() {
        this.key = null;
        this.value = null;
    }

    public Entry(String key, Object value) {
        this.key = key;
        this.value = value;
    }

    public String getKey() {
        return key;
    }

    public Object getValue() {
        return value;
    }

}

This is my adapter:

public class MapAdapter extends XmlAdapter<MyMap, Map<String, Object>> {

    @Override
    public MyMap marshal(Map<String, Object> v) throws Exception {
        MyMap myMap = new MyMap();

        for ( Map.Entry<String, Object> e : v.entrySet() ) {
            Entry entry = new Entry();
            entry.key = e.getKey();
            entry.value = e.getValue();

            myMap.entryList.add(entry);
        }
        return myMap;
    }

    @Override
    public Map<String, Object> unmarshal(MyMap v) throws Exception {
         Map<String, Object> map = new HashMap<String,Object>();
            for ( Entry e : v.entryList ) {
                map.put(e.key, e.value);
            }
     return map;
    }

}

But my wsdl is generating the following:

<xs:element minOccurs="0" name="foo" type="tns:testTO"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="testTO">
<xs:sequence>
<xs:element minOccurs="0" name="mapTest" type="tns:myMap"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="myMap">
<xs:complexContent>
<xs:extension base="tns:hashMap">
<xs:sequence>
<xs:element maxOccurs="unbounded" minOccurs="0" name="entryList" nillable="true" type="tns:entry"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="hashMap">
<xs:complexContent>
<xs:extension base="tns:abstractMap">
<xs:sequence/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType abstract="true" name="abstractMap">
<xs:sequence/>
</xs:complexType>
<xs:complexType name="entry">
<xs:sequence>
<xs:choice minOccurs="0">
<xs:element name="byte" type="xs:byte"/>
<xs:element name="short" type="xs:short"/>
<xs:element name="int" type="xs:int"/>
<xs:element name="long" type="xs:long"/>
<xs:element name="float" type="xs:float"/>
<xs:element name="double" type="xs:double"/>
<xs:element name="char" type="xs:unsignedShort"/>
<xs:element name="boolean" type="xs:boolean"/>
<xs:element name="ByteWrapper" type="xs:byte"/>
<xs:element name="ShortWrapper" type="xs:short"/>
<xs:element name="IntegerWrapper" type="xs:int"/>
<xs:element name="LongWrapper" type="xs:long"/>
<xs:element name="FloatWrapper" type="xs:float"/>
<xs:element name="DoubleWrapper" type="xs:double"/>
<xs:element name="Character" type="xs:unsignedShort"/>
<xs:element name="BooleanWrapper" type="xs:boolean"/>
<xs:element name="BigDecimal" type="xs:decimal"/>
<xs:element name="String" type="xs:string"/>
<xs:element name="Date" type="xs:dateTime"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="key" type="xs:string"/>
</xs:complexType>

I have looked at multiple other cases that I have found here and none of them were able to solve my problem I have even referenced http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html but the wsdl to java seems to be messing the schema up.

Thanks.

Answer

polbotinka picture polbotinka · May 6, 2015

I believe you don't have to write the custom XmlAdapter to marshall/unmarshall Map<String, Object with the newest JAXB versions. The below sample works fine for me.

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "foo")
public class Foo {
  private Map<String, Object> map = new HashMap<String, Object>();

  public Map<String, Object> getMap() {
    return params;
  }
}

This is resulted in a schema:

<xs:complexType name="foo">
  <xs:sequence>
    <xs:element name="map">
      <xs:complexType>
        <xs:sequence>
          <xs:element maxOccurs="unbounded" minOccurs="0" name="entry">
            <xs:complexType>
              <xs:sequence>
                <xs:element minOccurs="0" name="key" type="xs:string"/>
                <xs:element minOccurs="0" name="value" type="xs:anyType"/>
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:sequence>
      </xs:complexType>
    </xs:element>
  </xs:sequence>
</xs:complexType>

Then you should be able to unmarshall the following xml:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="http://your.org/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
   <soapenv:Header/>
   <soapenv:Body>
      <foo>
        <params>
            <entry>
               <key>string</key>
               <value xsi:type="xs:string">5</value>
             </entry>
             <entry>
                <key>integer</key>
                <value xsi:type="xs:int">54</value>
             </entry>
        </params>
      </foo>
   </soapenv:Body>
</soapenv:Envelope>

Just don't forget about xs and xsi namespaces. You can even pass your custom types as values not just simple xsi types. Then you have to make sure you have specified the proper xsi:type.