halp_me_plz
halp_me_plz

Reputation: 31

JAXB 4.X.X can't serialize parent class' field if same field exists on another child class

We've upgraded from JAXB 3.0.2 to 4.0.5 and serializing a field on a set of objects has started failing. I rolled back versions and it seams to first appear in JAXB 4.0.0.

The java objects are generated from a WSDL. The problem is that the Parent object has a field ID and ChildB has a field Id. When I try to serialize ChildA, the parent ID field is never serialized. If I remove the Id field from the definition of ChildB, then I can serialize ID (from Parent) when serializing ChildA.

If I roll back the JAXB version to 3.0.2, it has no problem serializing ID (Parent) when Id (ChildB) is defined. Or, if I am using JAXB 4.0.5 and I delete Id from ChildB, it works fine as well.

Why does a field existing in ChildB prevent ChildA from serializing a field on their shared Parent? Is there extra annotations or config I need to apply? I would like to avoid changing the WSDL if possible.

Parent.ID serialized - either JAXB 3.0.2 or ChildB.Id deleted:

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <SOAP-ENV:Body>
    <ns2:UpdateRequest xmlns:ns2="http://example.org/wsdl/testAPI">
      <ns2:Objects xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:ChildA">
        <ns2:ID>1234</ns2:ID>
        <ns2:Name>test-name</ns2:Name>
      </ns2:Objects>
    </ns2:UpdateRequest>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Parent.ID NOT serialized - JAXB 4.0.5 and ChildB.Id defined:

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Header/>
  <SOAP-ENV:Body>
    <ns2:UpdateRequest xmlns:ns2="http://example.org/wsdl/testAPI">
      <ns2:Objects xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:ChildA">
        <ns2:Name>test-name</ns2:Name>
      </ns2:Objects>
    </ns2:UpdateRequest>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Test Code:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>xml-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <jakarta.version>3.0.2</jakarta.version>
        <jakarta.xml.bind-api.version>4.0.2</jakarta.xml.bind-api.version>
        <wss4j.version>1.6.19</wss4j.version>
        <jaxb.version>4.0.5</jaxb.version> 
        <!-- <jaxb.version>3.0.2</jaxb.version> -->
        <spring-ws-security.version>4.0.11</spring-ws-security.version>
        <saaj-impl.version>3.0.4</saaj-impl.version>
        <spring.boot.version>3.2.2</spring.boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring.boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>

        <dependency>
            <groupId>jakarta.xml.soap</groupId>
            <artifactId>jakarta.xml.soap-api</artifactId>
            <version>${jakarta.version}</version>
        </dependency>
        <dependency>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
            <version>${jakarta.xml.bind-api.version}</version>
        </dependency>

<dependency>
    <groupId>javax.xml.ws</groupId>
    <artifactId>jaxws-api</artifactId>
    <version>2.3.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.jws</groupId>
    <artifactId>javax.jws-api</artifactId>
    <version>1.1</version>
    <scope>provided</scope>
</dependency>

        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>${jaxb.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.messaging.saaj</groupId>
            <artifactId>saaj-impl</artifactId>
            <version>${saaj-impl.version}</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>${jaxb.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws-core</artifactId>
            <version>${spring-ws-security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws-security</artifactId>
            <version>${spring-ws-security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.ws.security</groupId>
            <artifactId>wss4j</artifactId>
            <version>${wss4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ws</groupId>
            <artifactId>spring-ws-test</artifactId>
            <scope>test</scope>
            <version>4.0.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>6.1.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>5.2.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.10.1</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.1</version>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-commons</artifactId>
            <version>1.10.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <scope>compile</scope>
            <version>${spring.boot.version}</version>
        </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring.boot.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jvnet.jaxb</groupId>
                <artifactId>jaxb-maven-plugin</artifactId>
                <version>4.0.6</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.glassfish.jaxb</groupId>
                        <artifactId>jaxb-runtime</artifactId>
                        <version>${jaxb.version}</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <generatePackage>org.example</generatePackage>
                    <schemaDirectory>src/main/resources</schemaDirectory>
                    <schemaIncludes>
                        <include>*.wsdl</include>
                    </schemaIncludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

src/main/resources/test.wsdl

<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://example.org/wsdl/testAPI" targetNamespace="http://example.org/wsdl/testAPI">
  <types>
    <schema xmlns="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://example.org/wsdl/testAPI" version="1.0">
      <complexType name="Parent">
        <sequence>
          <element name="ID" type="xsd:int" minOccurs="0" maxOccurs="1"/>
        </sequence>
      </complexType>
      <complexType name="ChildA">
        <complexContent>
          <extension base="tns:Parent">
            <sequence>
              <element name="Name" minOccurs="0" type="xsd:string"/>
            </sequence>
          </extension>
        </complexContent>
      </complexType>
      <complexType name="Result">
        <sequence>
          <element name="StatusCode" type="xsd:string"/>
          <element name="StatusMessage" type="xsd:string"/>
        </sequence>
      </complexType>
      <element name="UpdateRequest">
        <complexType>
          <sequence>
            <element name="Objects" type="tns:Parent" minOccurs="1" maxOccurs="unbounded"/>
          </sequence>
        </complexType>
      </element>
      <complexType name="UpdateResult">
        <complexContent>
          <extension base="tns:Result">
            <sequence>
              <element name="Object" type="tns:Parent"/>
            </sequence>
          </extension>
        </complexContent>
      </complexType>
      <element name="UpdateResponse">
        <complexType>
          <sequence>
            <element name="Results" type="tns:UpdateResult" minOccurs="1" maxOccurs="unbounded"/>
          </sequence>
        </complexType>
      </element>
      <complexType name="ChildB">
        <complexContent>
          <extension base="tns:Parent">
            <sequence>
              <element name="Id" type="xsd:int" nillable="true" minOccurs="0" maxOccurs="1"/>
            </sequence>
          </extension>
        </complexContent>
      </complexType>
    </schema>
  </types>

  <message name="updateRequest">
    <part element="tns:UpdateRequest" name="parameters"/>
  </message>
  <message name="updateResponse">
    <part element="tns:UpdateResponse" name="parameters"/>
  </message>

  <portType name="Soap">
    <operation name="Update">
      <documentation>Update objects</documentation>
      <input message="tns:updateRequest"/>
      <output message="tns:updateResponse"/>
    </operation>
  </portType>
  <binding name="SoapBinding" type="tns:Soap">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    
    <operation name="Update">
      <soap:operation soapAction="Update"/>
      <input>
        <soap:body parts="parameters" use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  
  </binding>
  <service name="testAPI">
    <documentation>test API</documentation>
    <port binding="tns:SoapBinding" name="Soap">
      <soap:address location="https://example.org/Service.asmx"/>
    </port>
  </service>
</definitions>

src/test/java/org.example/XmlTest.java:

package org.example;

import com.sun.xml.messaging.saaj.soap.ver1_1.SOAPMessageFactory1_1Impl;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.context.DefaultMessageContext;
import org.springframework.ws.soap.saaj.SaajSoapMessageFactory;
import org.springframework.ws.support.MarshallingUtils;

import org.example.XmlTest.TestSoapConfiguration;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static org.springframework.ws.test.client.RequestMatchers.soapEnvelope;
import static org.springframework.ws.test.client.ResponseCreators.withSoapEnvelope;

@SpringBootTest(
        classes = {
                TestApplication.class,
                TestSoapConfiguration.class,
        },
        webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@ExtendWith(SpringExtension.class)
@ExtendWith(MockitoExtension.class)
public class XmlTest {

    @Test
    public void test() throws IOException {

        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        // this package must match the package in the <generatePackage> specified in pom.xml
        marshaller.setContextPath("org.example");

        ChildA ca = new ChildA();
        ca.setName("test-name");
        ca.setID(1234);

        UpdateRequest req = new UpdateRequest();
        req.getObjects().add(ca);

        DefaultMessageContext ctx = new DefaultMessageContext(new SaajSoapMessageFactory(new SOAPMessageFactory1_1Impl()));
        WebServiceMessage msg = ctx.getRequest();
        MarshallingUtils.marshal(marshaller, req, msg);
        ByteArrayOutputStream out = new ByteArrayOutputStream();;
        msg.writeTo(out);
        String str = out.toString(StandardCharsets.UTF_8);
        System.out.println(str);
    }

    @TestConfiguration
    public static class TestSoapConfiguration {
    }

src/test/java/org.example/TestApplication.java:

package org.example;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
@EnableConfigurationProperties
public class TestApplication {
    public TestApplication() {}

}

Upvotes: 3

Views: 57

Answers (0)

Related Questions