MarshallingAbstractTest.java
/*
* Copyright (c) 2026 Contributors to the Eclipse Foundation. All rights reserved.
* Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.glassfish.jaxb.runtime.v2.schemagen;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.XmlValue;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbConcreteContainer;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbConcreteDeployment;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbContainer;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbDistribution;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbEnvironmentModel;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
public class MarshallingAbstractTest {
@XmlSeeAlso({B.class, C.class})
abstract static class A {
//marshal/unmarshal of list elements containing elements of type = A will work without @XmlValue annotation...
@XmlValue
protected String value;
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ClassType1")
public static class B extends A {
public B() {}
public B(String value) {
this.value = value;
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "ClassType2")
public static class C extends A {
public C() {}
public C(String value) {
this.value = value;
}
}
@XmlRootElement(name="root")
@XmlAccessorType(XmlAccessType.FIELD)
static class Mapping {
@XmlElementWrapper(name = "list")
@XmlElement(name="element")
Collection<A> list = new ArrayList<>();
A element1;
A element2;
}
@Test
public void testMarshalSingleElement() throws Exception {
JAXBContext jc = JAXBContext.newInstance(Mapping.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter resultWriter = new StringWriter();
Mapping mapping = new Mapping();
mapping.element1 = new B("B1");
mapping.element2 = new C("C1");
marshaller.marshal(mapping, resultWriter);
String expectedXml1 = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<list/>
<element1 xsi:type="ClassType1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">B1</element1>
<element2 xsi:type="ClassType2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">C1</element2>
</root>
""";
assertEquals(expectedXml1, resultWriter.toString());
}
@Test
public void testMarshalCollection() throws Exception {
JAXBContext jc = JAXBContext.newInstance(Mapping.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter resultWriter = new StringWriter();
Mapping mapping = new Mapping();
mapping.element1 = new B("B1");
mapping.element2 = new C("C1");
mapping.list.add(new B("B"));
mapping.list.add(new C("C"));
String expectedXml2 = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<list>
<element xsi:type="ClassType1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">B</element>
<element xsi:type="ClassType2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">C</element>
</list>
<element1 xsi:type="ClassType1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">B1</element1>
<element2 xsi:type="ClassType2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">C1</element2>
</root>
""";
marshaller.marshal(mapping, resultWriter);
assertEquals(expectedXml2, resultWriter.toString());
}
@Test
public void testUnmarshalSingleElement() throws Exception {
JAXBContext jc = JAXBContext.newInstance(Mapping.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
//works without list..
String sourceXml1 = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<element1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ClassType1">B1</element1>
<element2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ClassType2">C1</element2>
</root>
""";
JAXBElement<Mapping> element = unmarshaller.unmarshal(new StreamSource(new StringReader(sourceXml1)), Mapping.class);
assertNotNull(element.getValue());
assertEquals(B.class, element.getValue().element1.getClass());
assertEquals(C.class, element.getValue().element2.getClass());
}
@Test
public void testUnmarshalCollection() throws Exception {
JAXBContext jc = JAXBContext.newInstance(Mapping.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
//don't work -> try to instantiate the abstract class
String sourceXml2 = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<root>
<list>
<element xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ClassType1">B</element>
<element xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ClassType2">C</element>
</list>
<element1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ClassType1">B1</element1>
<element2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ClassType2">C1</element2>
</root>
""";
try {
JAXBElement<Mapping> element = unmarshaller.unmarshal(new StreamSource(new StringReader(sourceXml2)), Mapping.class);
assertEquals(2, element.getValue().list.size());
}
catch (Throwable e) {
fail();
}
}
@Test
public void testXmlIDRefMarshal() throws Exception {
JAXBContext cont = JAXBContext.newInstance(JaxbEnvironmentModel.class);
JaxbEnvironmentModel envModel = new JaxbEnvironmentModel();
JaxbDistribution dist = new JaxbDistribution();
JaxbConcreteDeployment dep = new JaxbConcreteDeployment();
dep.setContextRoot("Context-Root");
dist.addDeployment(dep);
envModel.setDistribution(dist);
JaxbContainer container = new JaxbConcreteContainer();
container.addDeployment(dep);
envModel.setContainer(container);
String sourceXsd = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="environmentModel" type="environmentModelType"/>
<xs:complexType name="environmentModelType">
<xs:sequence>
<xs:element name="container" type="containerType" minOccurs="0"/>
<xs:element name="distribution" type="distributionType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="containerType" abstract="true">
<xs:sequence>
<xs:element name="deployments">
<xs:complexType>
<xs:sequence>
<xs:element name="deployment" type="xs:IDREF" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="deploymentType" abstract="true">
<xs:sequence>
<xs:element name="contextRoot" type="xs:ID"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="concreteDeploymentType">
<xs:complexContent>
<xs:extension base="deploymentType">
<xs:sequence/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="concreteContainerType">
<xs:complexContent>
<xs:extension base="containerType">
<xs:sequence/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="distributionType">
<xs:sequence>
<xs:element name="deployments">
<xs:complexType>
<xs:sequence>
<xs:element name="deployment" type="deploymentType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="mainTest">
<xs:sequence/>
</xs:complexType>
</xs:schema>""";
SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = sf.newSchema(new StreamSource(new StringReader(sourceXsd)));
Marshaller marshal = cont.createMarshaller();
marshal.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshal.setSchema(schema);
StringWriter resultWriter = new StringWriter();
marshal.marshal(envModel, resultWriter);
String expectedXml = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<environmentModel>
<container xsi:type="concreteContainerType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<deployments>
<deployment>Context-Root</deployment>
</deployments>
</container>
<distribution>
<deployments>
<deployment xsi:type="concreteDeploymentType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<contextRoot>Context-Root</contextRoot>
</deployment>
</deployments>
</distribution>
</environmentModel>
""";
assertEquals(expectedXml, resultWriter.toString());
}
}