Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 416
bhcmoney Avatar asked Apr 18 '13 14:04

bhcmoney


2 Answers

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.

like image 113
polbotinka Avatar answered Nov 19 '22 00:11

polbotinka


The solution I came up with that worked for what I was looking for was similar to what polbotinka mentioned but I added the additional bindings and adapters for dates. The TestTO class is extended by all of the objects on my interface as a way to have flexible attributes passed in a map format per object. Here is what I did:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="TestTO", namespace="test/common")
public abstract class TestTO {

    @XmlJavaTypeAdapter(MapAdapter.class)
    private Map<String, Object> elements;
}

This class would then generate a schema like the following (which was part of my overall WSDL generated by CXF-Java2Wsdl plugin):

<xs:complexType abstract="true" name="testTO">
<xs:sequence>
  <xs:element name="elements">
    <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>

I then used the following binding file during generation:

    <jaxws:bindings wsdlLocation="wsdl/TestImpl.wsdl" 
        xmlns:jaxws="http://java.sun.com/xml/ns/jaxws" 
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
        jaxb:version="2.1">
  <jaxws:bindings node="wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='http://namespace.goes.here']">
        <jaxb:bindings node="//xs:complexType[@name='testTO']//xs:element[@name='elements']">
            <jaxb:property>
                <jaxb:baseType name="java.util.Map&lt;String,Object&gt;" />
            </jaxb:property>
        </jaxb:bindings>
        <jaxb:serializable/>
  </jaxws:bindings>
</jaxws:bindings>

The generated version of the TestTO from CXF-Wsdl2Java looks as follows:

    @XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "testTO", propOrder = {
    "elements"
})
public abstract class TestTO {

    @XmlElement(required = true, type = TestTO.Elements.class)
    protected Map<String, Object> elements;

    public Map<String, Object> getElements() {
        return elements;
    }

    public void setElements(Map<String, Object> value) {
        this.elements = value;
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "", propOrder = {
        "entry"
    })
    public static class Elements {

        protected List<TestTO.Elements.Entry> entry;

        public List<TestTO.Elements.Entry> getEntry() {
            if (entry == null) {
                entry = new ArrayList<TestTO.Elements.Entry>();
            }
            return this.entry;
        }

        @XmlAccessorType(XmlAccessType.FIELD)
        @XmlType(name = "", propOrder = {
            "key",
            "value"
        })
        public static class Entry {

            protected java.lang.String key;
            protected java.lang.Object value;

            public java.lang.String getKey() {
                return key;
            }

            public void setKey(java.lang.String value) {
                this.key = value;
            }

            public java.lang.Object getValue() {
                return value;
            }

            public void setValue(java.lang.Object value) {
                this.value = value;
            }
        }
    }
}

So now the generated class has a Map to interface with that is automatically converted to the inner list class. So I then wanted to create an adapter to convert some of the datatypes into the types I wanted to use. Date was the specific one in this case as I usually used a JAXB binding file to do the dates conversion from the schema but since the schema was "anyType" that binding file would not work. So that MapAdapter.class from above was used to convert XmlGregorianCalendars on the input object within the map into Dates.

 public class MapAdapter extends XmlAdapter<TestTO.Elements, Map<String, Object>>{
    @Override
    public Map<String, Object> unmarshal(TestTO.Elements v) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();

        if(v != null && v.entry != null){
            for(Entry e : v.entry){
                if(e.getValue() instanceof XMLGregorianCalendar)
                    map.put(e.getKey(), ((XMLGregorianCalendar)e.getValue()).toGregorianCalendar().getTime());
                else
                    map.put(e.getKey(), e.getValue());
            }
        }
        return map;
    }

    @Override
    public TestTO.Elements marshal(Map<String, Object> v) throws Exception {
        TestTO.Elements b = new TestTO.Elements();
        if(v == null) 
            return null;
        for(java.util.Map.Entry<String, Object> e : v.entrySet()){
            Entry newEntry = new Entry();
            newEntry.setKey(e.getKey());
            newEntry.setValue(e.getValue());
            b.getEntry().add(newEntry);
        }
        return b;
    }
}

For this adapter to work I had to have a version of the "generated class" to mimic the inner Elements class. So I had a common.adapter.TestTO.class which was the generated one and a common.normal.TestTO.class which was the one that all the rest of my classes extended on my interface.

Here is the Plugin configuration I used in the client generation:

<plugin>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-codegen-plugin</artifactId>
                <version>${cxf.version}</version>
                <executions>
                    <execution>
                        <id>generate-sources</id>
                        <phase>generate-sources</phase>
                        <configuration>

                            <sourceRoot>${project.build.directory}/generated/cxf</sourceRoot>
                            <wsdlOptions>
                                <wsdlOption>
                                    <wsdl>${basedir}/src/main/resources/META-INF/wsdl/TestImpl.wsdl</wsdl>
                                    <bindingFiles>
                                        <bindingFile>${basedir}/src/main/resources/META-INF/binding.xml</bindingFile>
                                    </bindingFiles>
                                </wsdlOption>
                            </wsdlOptions>
                        </configuration>
                        <goals>
                            <goal>wsdl2java</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
like image 30
bhcmoney Avatar answered Nov 18 '22 23:11

bhcmoney