JAX-WS, @XmlJavaTypeAdapter, and a custom enum

enums, java, jax-ws, jaxb, soap

Solution

EnumBean

If `EnumBean` is the adapted class then it needs to have a default constructor.

package forum10972195;

public class EnumBean {

    int ordinal;
    String name;

    public EnumBean() {

    }

    public EnumBean(int ordinal, String name) {
        this.ordinal = ordinal;
        this.name = name;
    }

    public int getOrdinal() {
        return ordinal;
    }

    public void setOrdinal(int ordinal) {
        this.ordinal = ordinal;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

CustomEnumAdapter

Also the adapted class should appear as the first parameter when extending `XmlAdapter`. Since the parameter you are mapping to is of type `Day` you are going to need to have the `XmlAdapter` specify `Day` as the bound type.

package forum10972195;

import javax.xml.bind.annotation.adapters.XmlAdapter;

public class CustomEnumAdapter extends XmlAdapter<EnumBean, Day> {

    @Override
    public EnumBean marshal(Day v) throws Exception {
        EnumBean mine = new EnumBean(v.ordinal(), v.name());
        return mine;
    }

    @Override
    public Day unmarshal(EnumBean v) throws Exception {
        Day day = new Day(v.getOrdinal(),v.getName());
        return day;
    }

}

TEST CLIENT

With the changes I suggested, I ran your service in WebLogic 12.1.1 and with the built in test client I got the following:

Service Request

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
  <env:Header />
    <env:Body>
    <callEndpoint xmlns="http://forum10972195/">
      <!--Optional:-->
      <arg0 xmlns="">
        <!--Optional:-->
        <name>string</name>
        <ordinal>3</ordinal>
      </arg0>
    </callEndpoint>
  </env:Body>
</env:Envelope>

Service Response

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
    <S:Body>
    <ns0:callEndpointResponse xmlns:ns0="http://forum10972195/">
      <return>false</return>
    </ns0:callEndpointResponse>
  </S:Body>
</S:Envelope>

Problem

I am using a library that generates code based on IDL definitions. Its great to be able to use common enumerations in various languages (Java, C and C++), but these generated enums don't seem to play well with JAX-WS. Basically, main enum 'super' class has two members: ordinal and name. It looks similar to this (Note: this is in a third party library and is not JavaBean friendly) : ``` public class CustomEnum { int _ordinal; String _name; public CustomEnum(int ordinal, String name) { this._ordinal = ordinal; this._name = name; } public int ordinal() { return _ordinal; } public String name() { return _name; } } ``` So then in the generated code based on an IDL definition looks similar to this (Using Day as an example -- but in actuality I have about 50 'enums' that extend CustomEnum, so I'd like a solution that keeps me from having multiple copies of enums around such as an IDL generated type and a java.lang.enum): ``` public class Day extends CustomEnum { public static final Day Sunday = new Day(0, "Sunday"); public static final Day Monday = new Day(1, "Monday"); public static final Day Tuesday = new Day(2, "Tuesday"); public static final Day Wednesday = new Day(3, "Wednesday"); public static final Day Thursday = new Day(4, "Thursday"); public static final Day Friday = new Day(5, "Friday"); public static final Day Saturday = new Day(6, "Saturday"); public Day(int ordinal, String name) { super(ordinal, name); } } ``` Note that I also do not want to mess with/rearrange/annotate this class either since it is generated code. So, now what I want to do is to be able to use this 'Day' enum as a @WebParam in a JAX-WS @WebMethod. Here is a very simple example of what I want to be able to do: ``` import javax.jws.WebMethod; import javax.jws.WebService; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; @WebService public class EnumEndpoint { @WebMethod public boolean callEndpoint( @XmlJavaTypeAdapter(CustomEnumAdapter.class) Day day) { System.out.println(day.ordinal() + " " + day.name()); return false; } } ``` I was hoping by writing an XmlJavaTypeAdapter like so: ``` import java.lang.reflect.Type; import javax.xml.bind.annotation.adapters.XmlAdapter; public class CustomEnumAdapter extends XmlAdapter<CustomEnum, EnumBean> implements Type { @Override public EnumBean unmarshal(CustomEnum v) throws Exception { EnumBean mine = new EnumBean(v.ordinal(), v.name()); return mine; } @Override public CustomEnum marshal(EnumBean v) throws Exception { CustomEnum customEnum = new CustomEnum(v.getOrdinal(),v.getName()); return customEnum; } } ``` where EnumBean looks like: ``` public class EnumBean { int ordinal; String name; public EnumBean(int ordinal, String name) { this.ordinal = ordinal; this.name = name; } public int getOrdinal() { return ordinal; } public void setOrdinal(int ordinal) { this.ordinal = ordinal; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` but when I declare a Server Endpoint like this: ``` import javax.xml.ws.Endpoint; public class Server { /** * @param args */ public static void main(String[] args) { Endpoint.publish("http://0.0.0.0:7979/enum", new EnumEndpoint()); } } ``` I get this error: ``` Jun 10, 2012 3:29:21 PM com.sun.xml.internal.ws.model.RuntimeModeler getRequestWrapperClass INFO: Dynamically creating request wrapper Class test.jaxws.CallEndpoint Exception in thread "main" javax.xml.ws.WebServiceException: java.lang.IllegalArgumentException: value class test.CustomEnumAdapter at com.sun.xml.internal.ws.model.WrapperBeanGenerator.createRequestWrapperBean(WrapperBeanGenerator.java:249) at com.sun.xml.internal.ws.model.RuntimeModeler.getRequestWrapperClass(RuntimeModeler.java:280) at com.sun.xml.internal.ws.model.RuntimeModeler.processDocWrappedMethod(RuntimeModeler.java:674) at com.sun.xml.internal.ws.model.RuntimeModeler.processMethod(RuntimeModeler.java:612) at com.sun.xml.internal.ws.model.RuntimeModeler.processClass(RuntimeModeler.java:401) at com.sun.xml.internal.ws.model.RuntimeModeler.buildRuntimeModel(RuntimeModeler.java:240) at com.sun.xml.internal.ws.server.EndpointFactory.createSEIModel(EndpointFactory.java:312) at com.sun.xml.internal.ws.server.EndpointFactory.createEndpoint(EndpointFactory.java:178) at com.sun.xml.internal.ws.api.server.WSEndpoint.create(WSEndpoint.java:456) at com.sun.xml.internal.ws.api.server.WSEndpoint.create(WSEndpoint.java:475) at com.sun.xml.internal.ws.transport.http.server.EndpointImpl.createEndpoint(EndpointImpl.java:213) at com.sun.xml.internal.ws.transport.http.server.EndpointImpl.publish(EndpointImpl.java:143) at com.sun.xml.internal.ws.spi.ProviderImpl.createAndPublishEndpoint(ProviderImpl.java:102) at javax.xml.ws.Endpoint.publish(Endpoint.java:170) at test.Server.main(Server.java:11) Caused by: java.lang.IllegalArgumentException: value class test.CustomEnumAdapter at com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter.newConstItem(ClassWriter.java:893) at com.sun.xml.internal.ws.org.objectweb.asm.AnnotationWriter.visit(AnnotationWriter.java:185) at com.sun.xml.internal.ws.model.WrapperBeanGenerator.createBeanImage(WrapperBeanGenerator.java:111) at com.sun.xml.internal.ws.model.WrapperBeanGenerator.createRequestWrapperBean(WrapperBeanGenerator.java:245) ... 14 more ``` What am I missing here? I tried to use 'Day' directly, but since it doesn't have a default constructor and is not get/set friendly, that doesn't work either. Any tips?

Original source