You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@cxf.apache.org by "Hart, Andrew B." <AH...@Akimeka.com> on 2014/05/05 21:03:30 UTC

Code first, JAXB and design related questions

I've been wanting to ask this question of the list for some time, but I've put it  trying to see how far I could get.

Firstly, I am using a code first method and generating the WSDL.  CXF 2.3.1, JBoss6.  No options to upgrade further.

I have written an application level framework that makes it very easy for me to implement new web services and operations, which usually are just exposing some information from a database.  This framework takes care of auditing records of web service requests, supports requests of  asynchronous data extracts,  polling  the status of the extract, and returning results, optionally in "chunks".

In addition to the results, I wanted all of my web service responses to include some metadata about that request/response.   So, I created a "PayLoad" object which would contain a ResponseInfo object with the metadata and a List<PayLoadOjbect>.   So, all of my operations, across all web services return a PayLoad with its PayLoadList containing the results of their request.

Now, if you have an opinion on the general design of return results in an enclosing object with metadata like this,  whether or not there are other, better patterns of doing this or difficulties of consuming such services as a client, I'd be interested to hear them.   Otherwise...

...my problem is the list of results.  For some time I have had it returning

<PayLoad>
                <responseInfo> ...</responseInfo>
            <payLoadList>
               <payLoadObject xsi:type="ns2:MyBean" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                ...
</payLoadObject>
...
           </payLoadList>
</PayLoad>

My problem with this is that, even though all of the complex types returned are defined in the WSDL (because I have @XmlSeeAlso annotations in the service), there was no way for a consumer of the WSDL to determine exactly what type is returned by a specific operation.   Clients accessing the PayLoad list would need to "know" what type to expect in the payLoadList and cast them to the proper type.   (Clients have to have certs on file, so this seemed like a documentation problem to me.)  Also, I didn't like having the abstract super class, because if I needed to write a web service operation "aligned" with some external third party webservice, i.e.,  returned objects from other namespaces, I would have to modify them to extend that class to work with my framework.

So, next I got rid of PayLoadObject abstract class and just had my PayLoadList returning a list of Object.  The response XML looks exactly the same.   (I had not read about XML substitution groups, which sound promising and seem to require an abstract super class , but the aforementioned negatives still would be a consideration.  Is this the only way I can accomplish what I want and have it validate??)

Still, what I wanted was for the PayLoadList to look like *this*, with nodes of the proper element  name appearing in the payLoadList, not objects with a type attribute.  I am still quite a novice at writing web service clients, but in my first attempt, I had to cast the objects in the payLoadList to appropriate objects.  JAXB didn't unmarshal objects of the actual result type.

            <payLoadList>
               < ns2:MyBean>...</ns2:MyBean>
                ...
</payLoadObject>

The next thing I tried was parameterizing my PayLoad class, so that I would have my framework work with PayLoad<T>, my web service operations return PayLoad<ResultObjectClass>.    But the result was still the same, and not what I wanted.

So, along the lines of this blog tutorial, http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html, I created a ListWrapper<T> object, modified the List<T> in my PayLoad<T> to be a ListWrapper<T>.

In the ListWrapper<T>,  the get method for the enclosed list is annotated with @XmlAnyElement(lax=true)

Now, the XML being returned is exactly the way I want it!  But, my NEW problem is that my WSDL still doesn't seem to adequately describe what is being returned.    So, now my SoapUi test cases are all failing schema compliance assertions with an error along the lines of "Element not allowed: MyBean in element payLoadList".

So, in my WSDL:

<xs:complexType name="payLoad">
<xs:sequence>
<xs:element minOccurs="0" name="responseInfo" type="tns:wsResponseInfo" />
<xs:element minOccurs="0" name="payLoadList" type="tns:listWrapper" />
</xs:sequence>
</xs:complexType>
...
<xs:complexType name="listWrapper">
<xs:sequence>
<xs:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="lax" />
</xs:sequence>
</xs:complexType>


I don't know how to set namespace="##targetNamespace".  Would that solve my problem?  Is the substitution group approach
(like this: http://blog.bdoughan.com/2010/11/jaxb-and-inheritance-using-substitution.html) the only way (I'd have to resurrect my PayLoadObject)?  I'm not sure I understand this completely yet, but I'd like to avoid any dependencies of my framework classes, like PayLoad, on result classes defined in web service client modules.

Any suggestions or thoughts?



n  Andrew

RE: Code first, JAXB and design related questions

Posted by "Hart, Andrew B." <AH...@Akimeka.com>.
Update, I did try the substitution group approach and got the same results:

I reinstated the PayLoadObject abstract class super class, got rid of the ListWrapper and modified PayLoad to be defined as:

@XmlRootElement
@XmlSeeAlso({ PayLoadObject.class, PayLoadString.class })
@XmlType(propOrder = { "responseInfo", "payLoadList" })
public class PayLoad<T extends PayLoadObject> implements Serializable {

    private WSResponseInfo responseInfo;

    @XmlElementWrapper(name="payLoadList")
    @XmlElementRef()
    private List<T> payLoadList;

...

This causes the payLoadList to be generated as an anonymous complex type in the WSDL like this:

         <xs:complexType name="payLoad">
            <xs:sequence>
               <xs:element minOccurs="0" name="responseInfo" type="tns:wsResponseInfo"/>
               <xs:element minOccurs="0" name="payLoadList">
                  <xs:complexType>
                     <xs:sequence>
                        <xs:choice maxOccurs="unbounded" minOccurs="0">
                           <xs:element ref="tns:payLoadObject"/>
                           <xs:element ref="tns:payLoadString"/>
                           <xs:element ref="tns:myBean"/>
                        </xs:choice>
                     </xs:sequence>
                  </xs:complexType>
               </xs:element>
            </xs:sequence>
         </xs:complexType>

This also produces the response with the desired element node names in the payLoadList, just as the ListWrapper approach did.   But, the response:

      <ns2:getMyBeanDataResponse xmlns:ns2="http://mycompany.com">
         <payLoad>
            <responseInfo>
               <requestId>1026</requestId>
               <service>MyBeanService</service>
               <endpointHandler>GenericPayloadHandler</endpointHandler>
               <operation>GetMyBeanData</operation>
               <parameters><![CDATA[<?xml version="1.0" encoding="UTF-8" standalone="yes"?><requestParameters><ipAddress>10.5.20.109</ipAddress><parameters/></requestParameters>]]></parameters>
               <status>COMPLETED</status>
               <rowCount>240</rowCount>
               <purged>false</purged>
               <statusMessage>The request has completed successfully.</statusMessage>
               <startTimestamp>2014-05-05T15:02:35.450-05:00</startTimestamp>
               <endTimestamp>2014-05-05T15:02:35.521-05:00</endTimestamp>
               <asynchronous>false</asynchronous>
               <expiration>2014-05-05T15:02:35.521-05:00</expiration>
            </responseInfo>
            <payLoadList>
               <ns2:myBean>
                [..]
               </ns2: myBean >
               <ns2: myBean >
                [..]
               </ns2: myBean >
...

...still fails WSDL validation with error messages like:

line 75: Element not allowed: myBean@http://mycompany.com in element payLoadList



Regards,   Andrew