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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user