4

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.

Community
  • 1
  • 1
рüффп
  • 5,172
  • 34
  • 67
  • 113
  • Did you try to delete the propOrder attribute (maybe just manually for testing)? – Hein Blöd Jun 20 '15 at 11:05
  • I tried to create my own Jaxb's without propOrder but still the same result. Still expecting the additionnal fields. I saw I can pass my own binding.xjb file but I have no clue what to configure there (lacks of detailed documentation)... – рüффп Jun 22 '15 at 13:36
  • I made a simple unit test and the propOrder does not have any effect on the unmarshalling: the JAXB can still be instanciated from a XML with extra fields. Then my problem is probably focused on the unmarshalling of CXF but it seems I have no control on this part (auto-generated code) – рüффп Jun 24 '15 at 08:16
  • (I guess you meant to say '... can still NOT be instantiated ...') That's a pity. I've only used the EclipseLink/MOXy JAXB implementation so far, which simply ignores any unmapped elements during the unmarshalling. – Hein Blöd Jun 24 '15 at 09:15
  • No, the unit test is working fine but it does not use CXF nor WSDL (just a simple unmarshalling to JAX-B using javax.xml.bind.Unmarshaller and JaxbContext) – рüффп Jun 24 '15 at 09:44
  • I added more detail about how we are calling the Web-service using the CXF generated code – рüффп Jun 25 '15 at 07:24

3 Answers3

6

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.

рüффп
  • 5,172
  • 34
  • 67
  • 113
2

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

Sarfaraz Khan
  • 2,166
  • 2
  • 14
  • 29
  • Ok, but how to make CXF to generate this annotation? I don't want to modify the auto generated code. – рüффп Jul 02 '15 at 10:31
  • @ruffp Not sure how to it using cxf-codegen-plugin need to investigate on it.I would suggest to ask another question with as how to generate @XmlAnyElement(lax = true) using cxf-codegen-plugin – Sarfaraz Khan Jul 02 '15 at 10:49
  • Thanks for your advice but finally I managed to make it work just by adding a specific validator. – рüффп Jul 13 '15 at 07:27
1

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."

John
  • 316
  • 4
  • 6