Pass HashMap <String, Object> on top of SOAP with JAXB
I am trying to pass a Hashmap on top of SOAP. I am using CXF wsdl2java to create my schema. And I created a wrapper class for my HashMap, since the Hashmap itself cannot be passed along the line.
Then I created adapters to turn the Hashmap into a known type for my wsdl, but when my wsdl is created, it adds some unnecessary abstract map. Below is the code:
Here is my wrapper class for 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 the type of schema is known
@XmlJavaTypeAdapter(MapAdapter.class) public class MyMap extends HashMap<String, Object>{ public final List<Entry> entryList = new ArrayList<Entry>(); } This is the input class in which this 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 generates 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 looked at several other cases that I found here, and none of them could solve my problem. I even referred to http://docs.oracle.com/javase/6/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html but wsdl in java seems to be confusing the scheme.
Thanks.
The solution I came across worked for what I was looking for, it looked like polbotinka was mentioned, but I added extra bindings and adapters for the dates. The TestTO class is extended by all objects on my interface as a way to have flexible attributes passed in map format for each 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; } Then this class will generate a circuit similar to the following (which was part of my common WSDL generated by the 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> Then I 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<String,Object>" /> </jaxb:property> </jaxb:bindings> <jaxb:serializable/> </jaxws:bindings> </jaxws:bindings> The generated version of TestTO from CXF-Wsdl2Java is 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 interface, which is automatically converted to an internal list class. Therefore, I then wanted to create an adapter to convert some data types to the types that I wanted to use. The date was specific in this case, since I usually used the JAXB binding file to convert dates from the schema, but since the schema was "anyType", the binding file did not work. So the MapAdapter.class above was used to convert the XmlGregorianCalendars to the input object in the map in 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 class of Elements. So I had common.adapter.TestTO.class, which was generated, and common.normal.TestTO.class, which was what all the rest of my classes distributed on my interface.
Here is the plugin configuration I used in 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> I believe you do not need to write a custom XmlAdapter in marshall / unmarshall Map<String, Object with the latest versions of JAXB. The following example is great 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 shown in the diagram:
<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 can unmount 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 remember the xs and xsi namespaces. You can even pass your custom types as values of more than just simple xsi types. Then you must make sure to specify the correct xsi:type.