How to make CXF generated code to unmarshal into JAX-B and ignore unmapped fields?

5k views Asked by At

I call a web-service which can periodically add some element in their contract.

Example: the SOAP response body contains:

<picture>
   <active>true</active>
   <code>172</code>
   <label>Nikon D3200 Black</label>
   <downloadEnabled>true</downloadEnabled>
</picture>
<picture>
   <active>false</active>
   <code>177</code>
   <label>Nikon D5200 Red</label>
   <downloadEnabled>true</downloadEnabled>
   <extraField>none</extraField>
</picture>

and my CXF generated JAXB Bean looks like this:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "pictureListJAXB", propOrder = {
    "active",
    "code",
    "label",
    "downloadEnabled",
    "extraField"
})
public class PictureListJAXB {

    protected boolean active;
    protected String code;
    protected String label;
    protected boolean downloadEnabled;
    @XmlElement(nillable = true)
    protected String extraField

    // And Getters / Setters comes here after      

}

The JAXB beans are generated with the maven plugin cxf-codegen-plugin version 2.6.2 (from apache.cxf).

Now I want to know if there is a solution to make my Bean to be fault tolerant in case a new element appears in the SOAP response:

<picture>
    <active>true</active>
    <code>172</code>
    <label>Nikon D3200 Black</label>
    <downloadEnabled>true</downloadEnabled>
    <newUnmappedElement>anything irrelevant</newUnmappedElement>
</picture>

For now, when I recieve such response, I got an Unmarshalling Error because this new element.

My JAXB contains the minimal fields I want to manage but I want the bean to be able to cope with new element and just ignore them.

Is there any way to do it without regenerating the JAXB Bean? (As now I have to regenerate the Beans and release my project)

I checked the CXF options (and xjc) and found nothing in the doc (and google). The unmarshalling operation is automatically done in the ReferentialService also generated by CXF, then an option to modify the generation of this part would be sufficient.

Here is the code to call the web-service using the CXF generated classes:

public ReferentialService getReferentialService(String resource, String auth) throws RuntimeException {

    // These two classes are generated by CXF
    Referential referential;
    ReferentialService referentialService;


    // Get the resource URL in form http://myserver:port/remote-backend/resource?wsdl
    referential = new Referential(new URL(MyConfigUtil.getWSDL(resource)));

    referentialService = referential.getReferentialServicePort();
    BindingProvider bp = (BindingProvider) referentialService;

    // Get the endpoint URL in form http://myserver:port/remote-backend/resource
    bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, MyConfigUtil.getWebServiceEndPoint(resource));

    // Add SOAP credentials
    String username = HttpBasicAuthHelper.getUsername(auth);
    String password = HttpBasicAuthHelper.getPassword(auth);

    bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, username);
    bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);

    return referentialService;
}

and the call:

// Throws Exception just for the sample code
public InputStream listPictures(QueryDTO request, String resource, String auth) throws Exception {

    InputStream is = null;
    if (request != null) {

        // This is the WS Call which failed with unmarshal error
        List<PictureListJAXB> result = getReferentialService(resource, auth).getPictures(request);

        // Some code to convert JAXB into a  XML stream
        is = convertObjectToXmlStream(result);
    }
    return is;
}

UPDATE: I saw this post and my feeling was the same: JAXB will just ignore the unmapped elements if used alone without CXF. By using the cxf-codegen-plugin, it is not the case.

3

There are 3 answers

2
рüффп On BEST ANSWER

I finally found the answer by looking to this another post but as I do not use the declarative way like in the post, it makes me guessed I should be able to add some properties on the binding provider.

My modification to my code is to add these properties in the BindingProvider like this in the getReferentialService method:

bp.getRequestContext().put("schema-validation-enabled", "true");
bp.getRequestContext().put("jaxb-validation-event-handler", new ValidatorHandler());

and for the test, I just created an inner class ValidatorHandler:

private class ValidatorHandler extends DefaultValidationEventHandler {
    @Override
    public boolean handleEvent(ValidationEvent event) {
        if (event.getSeverity() == ValidationEvent.WARNING) {
            return super.handleEvent(event);
        } else if (event.getSeverity() == ValidationEvent.ERROR
                && event.getMessage().startsWith("unexpected element")) {
            return true;
        } else {
            throw new RuntimeException(event.getMessage()
                    + " [line:" + event.getLocator().getLineNumber() + "]");
        }
    }
}

By doing this, I do not need to modify my generated beans (JAX-B) and use them as they were generated by default.

0
John On

Your answer was helpful in my research. Thanks. I had the same issue of unknown elements in the SOAP response specifically

ValidationEvent.FATAL_ERROR "cvc-complex-type.2.4.a: Invalid content"

I was able to add the following

bp.getRequestContext().put("set-jaxb-validation-event-handler", false);

This will turn off the just JAXB validation and quoting from Danial Kulp CXF commiter "For the most part, that is just things like unknown elements or elements in wrong namespaces and such."

3
Sarfaraz Khan On

One way to solve this issue is to use Jaxb annotation @XmlAnyElement(lax = true) .This means that for that field if an element is associated with a class via @XmlRootElement or @XmlElementDecl then an instance of the corresponding class will be used to populate the field if not the element will be set as an instance of org.w3c.dom.Element.A sample code

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
    ..
    ....
    @XmlAnyElement(lax = true)
    protected List<Object> any;

Any elements in the input that do not correspond to explicit properties of the class will be swept up into this list.Checkout more enter link description here