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:
Arjen Poutsma 2023-03-23 16:54:26 +01:00
parent 7df2e2a8d2
commit 9f85e397d4
12 changed files with 411 additions and 177 deletions

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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"));
}
}

View File

@ -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());
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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 {
}

View File

@ -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;