Support XmlSeeAlso in Jaxb2XmlDecoder
This commit adds support for the @XmlSeeAlso annotation in the Jaxb2XmlDecoder. This includes - Finding the set of possible qualified names given a class name, rather than a single name. - Splitting the XMLEvent stream when coming across one of the names in this set. Closes gh-30167
This commit is contained in:
parent
7df2e2a8d2
commit
9f85e397d4
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.codec.xml;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlSchema;
|
||||
import jakarta.xml.bind.annotation.XmlSeeAlso;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.SynchronousSink;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Helper class for JAXB2.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 6.1
|
||||
*/
|
||||
abstract class Jaxb2Helper {
|
||||
|
||||
/**
|
||||
* The default value for JAXB annotations.
|
||||
* @see XmlRootElement#name()
|
||||
* @see XmlRootElement#namespace()
|
||||
* @see XmlType#name()
|
||||
* @see XmlType#namespace()
|
||||
*/
|
||||
private static final String JAXB_DEFAULT_ANNOTATION_VALUE = "##default";
|
||||
|
||||
|
||||
/**
|
||||
* Returns the set of qualified names for the given class, according to the
|
||||
* mapping rules in the JAXB specification.
|
||||
*/
|
||||
public static Set<QName> toQNames(Class<?> clazz) {
|
||||
Set<QName> result = new HashSet<>(1);
|
||||
findQNames(clazz, result, new HashSet<>());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void findQNames(Class<?> clazz, Set<QName> qNames, Set<Class<?>> completedClasses) {
|
||||
// safety against circular XmlSeeAlso references
|
||||
if (completedClasses.contains(clazz)) {
|
||||
return;
|
||||
}
|
||||
if (clazz.isAnnotationPresent(XmlRootElement.class)) {
|
||||
XmlRootElement annotation = clazz.getAnnotation(XmlRootElement.class);
|
||||
qNames.add(new QName(namespace(annotation.namespace(), clazz),
|
||||
localPart(annotation.name(), clazz)));
|
||||
}
|
||||
else if (clazz.isAnnotationPresent(XmlType.class)) {
|
||||
XmlType annotation = clazz.getAnnotation(XmlType.class);
|
||||
qNames.add(new QName(namespace(annotation.namespace(), clazz),
|
||||
localPart(annotation.name(), clazz)));
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Output class [" + clazz.getName() +
|
||||
"] is neither annotated with @XmlRootElement nor @XmlType");
|
||||
}
|
||||
completedClasses.add(clazz);
|
||||
if (clazz.isAnnotationPresent(XmlSeeAlso.class)) {
|
||||
XmlSeeAlso annotation = clazz.getAnnotation(XmlSeeAlso.class);
|
||||
for (Class<?> seeAlso : annotation.value()) {
|
||||
findQNames(seeAlso, qNames, completedClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String localPart(String value, Class<?> outputClass) {
|
||||
if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(value)) {
|
||||
return ClassUtils.getShortNameAsProperty(outputClass);
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static String namespace(String value, Class<?> outputClass) {
|
||||
if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(value)) {
|
||||
Package outputClassPackage = outputClass.getPackage();
|
||||
if (outputClassPackage != null && outputClassPackage.isAnnotationPresent(XmlSchema.class)) {
|
||||
XmlSchema annotation = outputClassPackage.getAnnotation(XmlSchema.class);
|
||||
return annotation.namespace();
|
||||
}
|
||||
else {
|
||||
return XMLConstants.NULL_NS_URI;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a flux of {@link XMLEvent XMLEvents} into a flux of XMLEvent lists, one list
|
||||
* for each branch of the tree that starts with one of the given qualified names.
|
||||
* That is, given the XMLEvents shown {@linkplain XmlEventDecoder here},
|
||||
* and the name "{@code child}", this method returns a flux
|
||||
* of two lists, each of which containing the events of a particular branch
|
||||
* of the tree that starts with "{@code child}".
|
||||
* <ol>
|
||||
* <li>The first list, dealing with the first branch of the tree:
|
||||
* <ol>
|
||||
* <li>{@link javax.xml.stream.events.StartElement} {@code child}</li>
|
||||
* <li>{@link javax.xml.stream.events.Characters} {@code foo}</li>
|
||||
* <li>{@link javax.xml.stream.events.EndElement} {@code child}</li>
|
||||
* </ol>
|
||||
* <li>The second list, dealing with the second branch of the tree:
|
||||
* <ol>
|
||||
* <li>{@link javax.xml.stream.events.StartElement} {@code child}</li>
|
||||
* <li>{@link javax.xml.stream.events.Characters} {@code bar}</li>
|
||||
* <li>{@link javax.xml.stream.events.EndElement} {@code child}</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* </ol>
|
||||
*/
|
||||
public static Flux<List<XMLEvent>> split(Flux<XMLEvent> xmlEventFlux, Set<QName> names) {
|
||||
return xmlEventFlux.handle(new SplitHandler(names));
|
||||
}
|
||||
|
||||
|
||||
private static class SplitHandler implements BiConsumer<XMLEvent, SynchronousSink<List<XMLEvent>>> {
|
||||
|
||||
private final Set<QName> names;
|
||||
|
||||
@Nullable
|
||||
private List<XMLEvent> events;
|
||||
|
||||
private int elementDepth = 0;
|
||||
|
||||
private int barrier = Integer.MAX_VALUE;
|
||||
|
||||
public SplitHandler(Set<QName> names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(XMLEvent event, SynchronousSink<List<XMLEvent>> sink) {
|
||||
if (event.isStartElement()) {
|
||||
if (this.barrier == Integer.MAX_VALUE) {
|
||||
QName startElementName = event.asStartElement().getName();
|
||||
if (this.names.contains(startElementName)) {
|
||||
this.events = new ArrayList<>();
|
||||
this.barrier = this.elementDepth;
|
||||
}
|
||||
}
|
||||
this.elementDepth++;
|
||||
}
|
||||
if (this.elementDepth > this.barrier) {
|
||||
Assert.state(this.events != null, "No XMLEvent List");
|
||||
this.events.add(event);
|
||||
}
|
||||
if (event.isEndElement()) {
|
||||
this.elementDepth--;
|
||||
if (this.elementDepth == this.barrier) {
|
||||
this.barrier = Integer.MAX_VALUE;
|
||||
Assert.state(this.events != null, "No XMLEvent List");
|
||||
sink.next(this.events);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,10 +21,9 @@ import java.util.ArrayList;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.stream.XMLEventReader;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
|
@ -36,13 +35,12 @@ import jakarta.xml.bind.JAXBException;
|
|||
import jakarta.xml.bind.UnmarshalException;
|
||||
import jakarta.xml.bind.Unmarshaller;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlSchema;
|
||||
import jakarta.xml.bind.annotation.XmlSeeAlso;
|
||||
import jakarta.xml.bind.annotation.XmlType;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.SynchronousSink;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.AbstractDecoder;
|
||||
|
@ -54,9 +52,8 @@ import org.springframework.core.io.buffer.DataBufferLimitException;
|
|||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.log.LogFormatUtils;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.util.xml.StaxUtils;
|
||||
|
@ -72,15 +69,6 @@ import org.springframework.util.xml.StaxUtils;
|
|||
*/
|
||||
public class Jaxb2XmlDecoder extends AbstractDecoder<Object> {
|
||||
|
||||
/**
|
||||
* The default value for JAXB annotations.
|
||||
* @see XmlRootElement#name()
|
||||
* @see XmlRootElement#namespace()
|
||||
* @see XmlType#name()
|
||||
* @see XmlType#namespace()
|
||||
*/
|
||||
private static final String JAXB_DEFAULT_ANNOTATION_VALUE = "##default";
|
||||
|
||||
private static final XMLInputFactory inputFactory = StaxUtils.createDefensiveInputFactory();
|
||||
|
||||
|
||||
|
@ -162,8 +150,8 @@ public class Jaxb2XmlDecoder extends AbstractDecoder<Object> {
|
|||
inputStream, ResolvableType.forClass(XMLEvent.class), mimeType, hints);
|
||||
|
||||
Class<?> outputClass = elementType.toClass();
|
||||
QName typeName = toQName(outputClass);
|
||||
Flux<List<XMLEvent>> splitEvents = split(xmlEventFlux, typeName);
|
||||
Set<QName> typeNames = Jaxb2Helper.toQNames(outputClass);
|
||||
Flux<List<XMLEvent>> splitEvents = Jaxb2Helper.split(xmlEventFlux, typeNames);
|
||||
|
||||
return splitEvents.map(events -> {
|
||||
Object value = unmarshal(events, outputClass);
|
||||
|
@ -184,6 +172,7 @@ public class Jaxb2XmlDecoder extends AbstractDecoder<Object> {
|
|||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Object decode(DataBuffer dataBuffer, ResolvableType targetType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) throws DecodingException {
|
||||
|
||||
|
@ -229,7 +218,8 @@ public class Jaxb2XmlDecoder extends AbstractDecoder<Object> {
|
|||
try {
|
||||
Unmarshaller unmarshaller = initUnmarshaller(outputClass);
|
||||
XMLEventReader eventReader = StaxUtils.createXMLEventReader(events);
|
||||
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
|
||||
if (outputClass.isAnnotationPresent(XmlRootElement.class) ||
|
||||
outputClass.isAnnotationPresent(XmlSeeAlso.class)) {
|
||||
return unmarshaller.unmarshal(eventReader);
|
||||
}
|
||||
else {
|
||||
|
@ -250,113 +240,4 @@ public class Jaxb2XmlDecoder extends AbstractDecoder<Object> {
|
|||
return this.unmarshallerProcessor.apply(unmarshaller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the qualified name for the given class, according to the mapping rules
|
||||
* in the JAXB specification.
|
||||
*/
|
||||
QName toQName(Class<?> outputClass) {
|
||||
String localPart;
|
||||
String namespaceUri;
|
||||
|
||||
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
|
||||
XmlRootElement annotation = outputClass.getAnnotation(XmlRootElement.class);
|
||||
localPart = annotation.name();
|
||||
namespaceUri = annotation.namespace();
|
||||
}
|
||||
else if (outputClass.isAnnotationPresent(XmlType.class)) {
|
||||
XmlType annotation = outputClass.getAnnotation(XmlType.class);
|
||||
localPart = annotation.name();
|
||||
namespaceUri = annotation.namespace();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Output class [" + outputClass.getName() +
|
||||
"] is neither annotated with @XmlRootElement nor @XmlType");
|
||||
}
|
||||
|
||||
if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(localPart)) {
|
||||
localPart = ClassUtils.getShortNameAsProperty(outputClass);
|
||||
}
|
||||
if (JAXB_DEFAULT_ANNOTATION_VALUE.equals(namespaceUri)) {
|
||||
Package outputClassPackage = outputClass.getPackage();
|
||||
if (outputClassPackage != null && outputClassPackage.isAnnotationPresent(XmlSchema.class)) {
|
||||
XmlSchema annotation = outputClassPackage.getAnnotation(XmlSchema.class);
|
||||
namespaceUri = annotation.namespace();
|
||||
}
|
||||
else {
|
||||
namespaceUri = XMLConstants.NULL_NS_URI;
|
||||
}
|
||||
}
|
||||
return new QName(namespaceUri, localPart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a flux of {@link XMLEvent XMLEvents} into a flux of XMLEvent lists, one list
|
||||
* for each branch of the tree that starts with the given qualified name.
|
||||
* That is, given the XMLEvents shown {@linkplain XmlEventDecoder here},
|
||||
* and the {@code desiredName} "{@code child}", this method returns a flux
|
||||
* of two lists, each of which containing the events of a particular branch
|
||||
* of the tree that starts with "{@code child}".
|
||||
* <ol>
|
||||
* <li>The first list, dealing with the first branch of the tree:
|
||||
* <ol>
|
||||
* <li>{@link javax.xml.stream.events.StartElement} {@code child}</li>
|
||||
* <li>{@link javax.xml.stream.events.Characters} {@code foo}</li>
|
||||
* <li>{@link javax.xml.stream.events.EndElement} {@code child}</li>
|
||||
* </ol>
|
||||
* <li>The second list, dealing with the second branch of the tree:
|
||||
* <ol>
|
||||
* <li>{@link javax.xml.stream.events.StartElement} {@code child}</li>
|
||||
* <li>{@link javax.xml.stream.events.Characters} {@code bar}</li>
|
||||
* <li>{@link javax.xml.stream.events.EndElement} {@code child}</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* </ol>
|
||||
*/
|
||||
Flux<List<XMLEvent>> split(Flux<XMLEvent> xmlEventFlux, QName desiredName) {
|
||||
return xmlEventFlux.handle(new SplitHandler(desiredName));
|
||||
}
|
||||
|
||||
|
||||
private static class SplitHandler implements BiConsumer<XMLEvent, SynchronousSink<List<XMLEvent>>> {
|
||||
|
||||
private final QName desiredName;
|
||||
|
||||
@Nullable
|
||||
private List<XMLEvent> events;
|
||||
|
||||
private int elementDepth = 0;
|
||||
|
||||
private int barrier = Integer.MAX_VALUE;
|
||||
|
||||
public SplitHandler(QName desiredName) {
|
||||
this.desiredName = desiredName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(XMLEvent event, SynchronousSink<List<XMLEvent>> sink) {
|
||||
if (event.isStartElement()) {
|
||||
if (this.barrier == Integer.MAX_VALUE) {
|
||||
QName startElementName = event.asStartElement().getName();
|
||||
if (this.desiredName.equals(startElementName)) {
|
||||
this.events = new ArrayList<>();
|
||||
this.barrier = this.elementDepth;
|
||||
}
|
||||
}
|
||||
this.elementDepth++;
|
||||
}
|
||||
if (this.elementDepth > this.barrier) {
|
||||
Assert.state(this.events != null, "No XMLEvent List");
|
||||
this.events.add(event);
|
||||
}
|
||||
if (event.isEndElement()) {
|
||||
this.elementDepth--;
|
||||
if (this.elementDepth == this.barrier) {
|
||||
this.barrier = Integer.MAX_VALUE;
|
||||
Assert.state(this.events != null, "No XMLEvent List");
|
||||
sink.next(this.events);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.codec.xml;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElement;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElementWithName;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElementWithNameAndNamespace;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlType;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlTypeSeeAlso;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlTypeWithName;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlTypeWithNameAndNamespace;
|
||||
import org.springframework.web.testfixture.xml.Pojo;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
class Jaxb2HelperTests {
|
||||
|
||||
@Test
|
||||
public void toExpectedQName() {
|
||||
assertThat(Jaxb2Helper.toQNames(Pojo.class)).containsExactly(new QName("pojo"));
|
||||
assertThat(Jaxb2Helper.toQNames(TypePojo.class)).containsExactly(new QName("pojo"));
|
||||
|
||||
assertThat(Jaxb2Helper.toQNames(XmlRootElementWithNameAndNamespace.class)).containsExactly(new QName("namespace-type", "name-type"));
|
||||
assertThat(Jaxb2Helper.toQNames(XmlRootElementWithName.class)).containsExactly(new QName("namespace-package", "name-type"));
|
||||
assertThat(Jaxb2Helper.toQNames(XmlRootElement.class)).containsExactly(new QName("namespace-package", "xmlRootElement"));
|
||||
|
||||
assertThat(Jaxb2Helper.toQNames(XmlTypeWithNameAndNamespace.class)).containsExactly(new QName("namespace-type", "name-type"));
|
||||
assertThat(Jaxb2Helper.toQNames(XmlTypeWithName.class)).containsExactly(new QName("namespace-package", "name-type"));
|
||||
assertThat(Jaxb2Helper.toQNames(XmlType.class)).containsExactly(new QName("namespace-package", "xmlType"));
|
||||
assertThat(Jaxb2Helper.toQNames(XmlTypeSeeAlso.class)).containsExactlyInAnyOrder(new QName("namespace-package", "xmlTypeSeeAlso"),
|
||||
new QName("namespace-package", "name-type"), new QName("namespace-type", "name-type"));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -20,10 +20,13 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.xml.namespace.QName;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlSeeAlso;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
@ -35,13 +38,6 @@ import org.springframework.core.codec.DecodingException;
|
|||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElement;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElementWithName;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlRootElementWithNameAndNamespace;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlType;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlTypeWithName;
|
||||
import org.springframework.http.codec.xml.jaxb.XmlTypeWithNameAndNamespace;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.web.testfixture.xml.Pojo;
|
||||
|
||||
|
@ -96,7 +92,7 @@ public class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
|||
@Test
|
||||
public void splitOneBranches() {
|
||||
Flux<XMLEvent> xmlEvents = this.xmlEventDecoder.decode(toDataBufferMono(POJO_ROOT), null, null, HINTS);
|
||||
Flux<List<XMLEvent>> result = this.decoder.split(xmlEvents, new QName("pojo"));
|
||||
Flux<List<XMLEvent>> result = Jaxb2Helper.split(xmlEvents, Set.of(new QName("pojo")));
|
||||
|
||||
StepVerifier.create(result)
|
||||
.consumeNextWith(events -> {
|
||||
|
@ -117,7 +113,7 @@ public class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
|||
@Test
|
||||
public void splitMultipleBranches() {
|
||||
Flux<XMLEvent> xmlEvents = this.xmlEventDecoder.decode(toDataBufferMono(POJO_CHILD), null, null, HINTS);
|
||||
Flux<List<XMLEvent>> result = this.decoder.split(xmlEvents, new QName("pojo"));
|
||||
Flux<List<XMLEvent>> result = Jaxb2Helper.split(xmlEvents, Set.of(new QName("pojo")));
|
||||
|
||||
|
||||
StepVerifier.create(result)
|
||||
|
@ -208,6 +204,19 @@ public class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
|||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeXmlSeeAlso() {
|
||||
Mono<DataBuffer> source = toDataBufferMono(POJO_CHILD);
|
||||
Flux<Object> output = this.decoder.decode(source, ResolvableType.forClass(Parent.class), null, HINTS);
|
||||
|
||||
StepVerifier.create(output)
|
||||
.expectNext(new Child("foo", "bar"))
|
||||
.expectNext(new Child("foofoo", "barbar"))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeError() {
|
||||
Flux<DataBuffer> source = Flux.concat(
|
||||
|
@ -252,21 +261,6 @@ public class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
|||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toExpectedQName() {
|
||||
assertThat(this.decoder.toQName(Pojo.class)).isEqualTo(new QName("pojo"));
|
||||
assertThat(this.decoder.toQName(TypePojo.class)).isEqualTo(new QName("pojo"));
|
||||
|
||||
assertThat(this.decoder.toQName(XmlRootElementWithNameAndNamespace.class)).isEqualTo(new QName("namespace", "name"));
|
||||
assertThat(this.decoder.toQName(XmlRootElementWithName.class)).isEqualTo(new QName("namespace", "name"));
|
||||
assertThat(this.decoder.toQName(XmlRootElement.class)).isEqualTo(new QName("namespace", "xmlRootElement"));
|
||||
|
||||
assertThat(this.decoder.toQName(XmlTypeWithNameAndNamespace.class)).isEqualTo(new QName("namespace", "name"));
|
||||
assertThat(this.decoder.toQName(XmlTypeWithName.class)).isEqualTo(new QName("namespace", "name"));
|
||||
assertThat(this.decoder.toQName(XmlType.class)).isEqualTo(new QName("namespace", "xmlType"));
|
||||
|
||||
}
|
||||
|
||||
private Mono<DataBuffer> toDataBufferMono(String value) {
|
||||
return Mono.defer(() -> {
|
||||
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||
|
@ -276,20 +270,17 @@ public class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
@jakarta.xml.bind.annotation.XmlType(name = "pojo")
|
||||
public static class TypePojo {
|
||||
@jakarta.xml.bind.annotation.XmlType
|
||||
@XmlSeeAlso(Child.class)
|
||||
public static abstract class Parent {
|
||||
|
||||
private String foo;
|
||||
|
||||
private String bar;
|
||||
|
||||
public TypePojo() {
|
||||
public Parent() {
|
||||
}
|
||||
|
||||
public TypePojo(String foo, String bar) {
|
||||
public Parent(String foo) {
|
||||
this.foo = foo;
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public String getFoo() {
|
||||
|
@ -299,6 +290,20 @@ public class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
|||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
}
|
||||
|
||||
@jakarta.xml.bind.annotation.XmlRootElement(name = "pojo")
|
||||
public static class Child extends Parent {
|
||||
|
||||
private String bar;
|
||||
|
||||
public Child() {
|
||||
}
|
||||
|
||||
public Child(String foo, String bar) {
|
||||
super(foo);
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
|
@ -309,21 +314,21 @@ public class Jaxb2XmlDecoderTests extends AbstractLeakCheckingTests {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof TypePojo other) {
|
||||
return this.foo.equals(other.foo) && this.bar.equals(other.bar);
|
||||
if (o instanceof Child other) {
|
||||
return getBar().equals(other.getBar()) &&
|
||||
getFoo().equals(other.getFoo());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.foo.hashCode();
|
||||
result = 31 * result + this.bar.hashCode();
|
||||
return result;
|
||||
return Objects.hash(getBar(), getFoo());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -57,7 +57,7 @@ public class Jaxb2XmlEncoderTests extends AbstractEncoderTests<Jaxb2XmlEncoder>
|
|||
assertThat(this.encoder.canEncode(forClass(Pojo.class), new MediaType("application", "foo+xml"))).isTrue();
|
||||
assertThat(this.encoder.canEncode(forClass(Pojo.class), MediaType.APPLICATION_JSON)).isFalse();
|
||||
|
||||
assertThat(this.encoder.canEncode(forClass(Jaxb2XmlDecoderTests.TypePojo.class), MediaType.APPLICATION_XML)).isTrue();
|
||||
assertThat(this.encoder.canEncode(forClass(TypePojo.class), MediaType.APPLICATION_XML)).isTrue();
|
||||
assertThat(this.encoder.canEncode(forClass(getClass()), MediaType.APPLICATION_XML)).isFalse();
|
||||
|
||||
// SPR-15464
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.codec.xml;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@jakarta.xml.bind.annotation.XmlType(name = "pojo")
|
||||
public class TypePojo {
|
||||
|
||||
private String foo;
|
||||
|
||||
private String bar;
|
||||
|
||||
public TypePojo() {
|
||||
}
|
||||
|
||||
public TypePojo(String foo, String bar) {
|
||||
this.foo = foo;
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public String getFoo() {
|
||||
return this.foo;
|
||||
}
|
||||
|
||||
public void setFoo(String foo) {
|
||||
this.foo = foo;
|
||||
}
|
||||
|
||||
public String getBar() {
|
||||
return this.bar;
|
||||
}
|
||||
|
||||
public void setBar(String bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof TypePojo other) {
|
||||
return this.foo.equals(other.foo) && this.bar.equals(other.bar);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.foo.hashCode();
|
||||
result = 31 * result + this.bar.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,7 +21,7 @@ import jakarta.xml.bind.annotation.XmlRootElement;
|
|||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlRootElement(name = "name")
|
||||
@XmlRootElement(name = "name-type")
|
||||
public class XmlRootElementWithName {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,7 +21,7 @@ import jakarta.xml.bind.annotation.XmlRootElement;
|
|||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlRootElement(name = "name", namespace = "namespace")
|
||||
@XmlRootElement(name = "name-type", namespace = "namespace-type")
|
||||
public class XmlRootElementWithNameAndNamespace {
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.http.codec.xml.jaxb;
|
||||
|
||||
import jakarta.xml.bind.annotation.XmlSeeAlso;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@jakarta.xml.bind.annotation.XmlType
|
||||
@XmlSeeAlso({XmlRootElementWithName.class, XmlRootElementWithNameAndNamespace.class})
|
||||
public class XmlTypeSeeAlso {
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,7 +21,7 @@ import jakarta.xml.bind.annotation.XmlType;
|
|||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlType(name = "name")
|
||||
@XmlType(name = "name-type")
|
||||
public class XmlTypeWithName {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -21,7 +21,7 @@ import jakarta.xml.bind.annotation.XmlType;
|
|||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
@XmlType(name = "name", namespace = "namespace")
|
||||
@XmlType(name = "name-type", namespace = "namespace-type")
|
||||
public class XmlTypeWithNameAndNamespace {
|
||||
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
@jakarta.xml.bind.annotation.XmlSchema(namespace = "namespace")
|
||||
@jakarta.xml.bind.annotation.XmlSchema(namespace = "namespace-package")
|
||||
package org.springframework.http.codec.xml.jaxb;
|
||||
|
|
Loading…
Reference in New Issue