Browse Source

Merge pull request #77 from poutsma/refactor_jaxb2decoder

Improve Jaxb2Decoder
pull/1111/head
Arjen Poutsma 10 years ago
parent
commit
5f09a62950
  1. 2
      spring-web-reactive/build.gradle
  2. 211
      spring-web-reactive/src/main/java/org/springframework/core/codec/support/Jaxb2Decoder.java
  3. 53
      spring-web-reactive/src/main/java/org/springframework/core/codec/support/Jaxb2Encoder.java
  4. 56
      spring-web-reactive/src/main/java/org/springframework/core/codec/support/JaxbContextContainer.java
  5. 151
      spring-web-reactive/src/main/java/org/springframework/core/codec/support/ListBasedXMLEventReader.java
  6. 145
      spring-web-reactive/src/main/java/org/springframework/core/codec/support/XmlEventDecoder.java
  7. 3
      spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerAdapter.java
  8. 252
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/Jaxb2DecoderTests.java
  9. 43
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/Jaxb2EncoderTests.java
  10. 77
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/XmlEventDecoderTests.java
  11. 25
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlRootElement.java
  12. 27
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlRootElementWithName.java
  13. 27
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlRootElementWithNameAndNamespace.java
  14. 25
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlType.java
  15. 27
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlTypeWithName.java
  16. 27
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlTypeWithNameAndNamespace.java
  17. 18
      spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/package-info.java
  18. 56
      spring-web-reactive/src/test/java/org/springframework/web/reactive/method/annotation/RequestMappingIntegrationTests.java

2
spring-web-reactive/build.gradle

@ -105,6 +105,7 @@ dependencies { @@ -105,6 +105,7 @@ dependencies {
optional "org.eclipse.jetty:jetty-server:${jettyVersion}"
optional "org.eclipse.jetty:jetty-servlet:${jettyVersion}"
optional("org.freemarker:freemarker:2.3.23")
optional("com.fasterxml:aalto-xml:1.0.0")
provided "javax.servlet:javax.servlet-api:3.1.0"
@ -118,6 +119,7 @@ dependencies { @@ -118,6 +119,7 @@ dependencies {
}
testCompile "org.hamcrest:hamcrest-all:1.3"
testCompile "com.squareup.okhttp3:mockwebserver:3.0.1"
testCompile("xmlunit:xmlunit:1.6")
// Needed to run Javadoc without error
optional "org.apache.httpcomponents:httpclient:4.5.1"

211
spring-web-reactive/src/main/java/org/springframework/core/codec/support/Jaxb2Decoder.java

@ -16,30 +16,28 @@ @@ -16,30 +16,28 @@
package org.springframework.core.codec.support;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.xml.bind.JAXBContext;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlType;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.events.XMLEvent;
import org.reactivestreams.Publisher;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.CodecException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.support.DataBufferUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
@ -47,87 +45,170 @@ import org.springframework.util.MimeTypeUtils; @@ -47,87 +45,170 @@ import org.springframework.util.MimeTypeUtils;
* Decode from a bytes stream of XML elements to a stream of {@code Object} (POJO).
*
* @author Sebastien Deleuze
* @author Arjen Poutsma
* @see Jaxb2Encoder
*/
public class Jaxb2Decoder extends AbstractDecoder<Object> {
private final ConcurrentMap<Class<?>, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64);
/**
* The default value for JAXB annotations.
* @see XmlRootElement#name()
* @see XmlRootElement#namespace()
* @see XmlType#name()
* @see XmlType#namespace()
*/
private final static String JAXB_DEFAULT_ANNOTATION_VALUE = "##default";
private final XmlEventDecoder xmlEventDecoder = new XmlEventDecoder();
private final JaxbContextContainer jaxbContexts = new JaxbContextContainer();
public Jaxb2Decoder() {
super(MimeTypeUtils.APPLICATION_XML, MimeTypeUtils.TEXT_XML);
}
@Override
public boolean canDecode(ResolvableType type, MimeType mimeType, Object... hints) {
if (super.canDecode(type, mimeType, hints)) {
Class<?> outputClass = type.getRawClass();
return outputClass.isAnnotationPresent(XmlRootElement.class) ||
outputClass.isAnnotationPresent(XmlType.class);
}
else {
return false;
}
}
@Override
public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType type,
MimeType mimeType, Object... hints) {
Class<?> outputClass = type.getRawClass();
try {
Source source = processSource(
new StreamSource(DataBufferUtils.toInputStream(inputStream)));
Unmarshaller unmarshaller = createUnmarshaller(outputClass);
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
return Flux.just(unmarshaller.unmarshal(source));
}
else {
JAXBElement<?> jaxbElement = unmarshaller.unmarshal(source, outputClass);
return Flux.just(jaxbElement.getValue());
}
Flux<XMLEvent> xmlEventFlux =
this.xmlEventDecoder.decode(inputStream, null, mimeType);
QName typeName = toQName(outputClass);
Flux<List<XMLEvent>> splitEvents = split(xmlEventFlux, typeName);
return splitEvents.map(events -> unmarshal(events, outputClass));
}
/**
* 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();
}
catch (UnmarshalException ex) {
return Flux.error(
new CodecException("Could not unmarshal to [" + outputClass + "]: " + ex.getMessage(), ex));
else if (outputClass.isAnnotationPresent(XmlType.class)) {
XmlType annotation = outputClass.getAnnotation(XmlType.class);
localPart = annotation.name();
namespaceUri = annotation.namespace();
}
catch (JAXBException ex) {
return Flux.error(new CodecException("Could not instantiate JAXBContext: " +
ex.getMessage(), ex));
else {
throw new IllegalArgumentException("Outputclass [" + outputClass + "] is " +
"neither annotated with @XmlRootElement nor @XmlType");
}
}
protected Source processSource(Source source) {
if (source instanceof StreamSource) {
StreamSource streamSource = (StreamSource) source;
InputSource inputSource = new InputSource(streamSource.getInputStream());
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
return new SAXSource(xmlReader, inputSource);
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();
}
catch (SAXException ex) {
throw new CodecException("Error while processing the source", ex);
else {
namespaceUri = XMLConstants.NULL_NS_URI;
}
}
else {
return source;
}
return new QName(namespaceUri, localPart);
}
protected final Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException {
try {
JAXBContext jaxbContext = getJaxbContext(clazz);
return jaxbContext.createUnmarshaller();
}
catch (JAXBException ex) {
throw new CodecException("Could not create Unmarshaller for class " +
"[" + clazz + "]: " + ex.getMessage(), ex);
}
/**
* Split a flux of {@link XMLEvent}s 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
.flatMap(new Function<XMLEvent, Publisher<? extends List<XMLEvent>>>() {
private List<XMLEvent> events = null;
private int elementDepth = 0;
private int barrier = Integer.MAX_VALUE;
@Override
public Publisher<? extends List<XMLEvent>> apply(XMLEvent event) {
if (event.isStartElement()) {
if (this.barrier == Integer.MAX_VALUE) {
QName startElementName = event.asStartElement().getName();
if (desiredName.equals(startElementName)) {
this.events = new ArrayList<XMLEvent>();
this.barrier = this.elementDepth;
}
}
this.elementDepth++;
}
if (this.elementDepth > this.barrier) {
this.events.add(event);
}
if (event.isEndElement()) {
this.elementDepth--;
if (this.elementDepth == this.barrier) {
this.barrier = Integer.MAX_VALUE;
return Mono.just(this.events);
}
}
return Mono.empty();
}
});
}
protected final JAXBContext getJaxbContext(Class<?> clazz) {
Assert.notNull(clazz, "'clazz' must not be null");
JAXBContext jaxbContext = this.jaxbContexts.get(clazz);
if (jaxbContext == null) {
try {
jaxbContext = JAXBContext.newInstance(clazz);
this.jaxbContexts.putIfAbsent(clazz, jaxbContext);
private Object unmarshal(List<XMLEvent> eventFlux, Class<?> outputClass) {
try {
Unmarshaller unmarshaller = this.jaxbContexts.createUnmarshaller(outputClass);
XMLEventReader eventReader = new ListBasedXMLEventReader(eventFlux);
if (outputClass.isAnnotationPresent(XmlRootElement.class)) {
return unmarshaller.unmarshal(eventReader);
}
catch (JAXBException ex) {
throw new CodecException("Could not instantiate JAXBContext for class " +
"[" + clazz + "]: " + ex.getMessage(), ex);
else {
JAXBElement<?> jaxbElement =
unmarshaller.unmarshal(eventReader, outputClass);
return jaxbElement.getValue();
}
}
return jaxbContext;
catch (JAXBException ex) {
throw new CodecException(ex.getMessage(), ex);
}
}
}

53
spring-web-reactive/src/main/java/org/springframework/core/codec/support/Jaxb2Encoder.java

@ -18,12 +18,11 @@ package org.springframework.core.codec.support; @@ -18,12 +18,11 @@ package org.springframework.core.codec.support;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.MarshalException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@ -32,7 +31,6 @@ import org.springframework.core.ResolvableType; @@ -32,7 +31,6 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.codec.CodecException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferAllocator;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
@ -41,27 +39,43 @@ import org.springframework.util.MimeTypeUtils; @@ -41,27 +39,43 @@ import org.springframework.util.MimeTypeUtils;
* Encode from an {@code Object} stream to a byte stream of XML elements.
*
* @author Sebastien Deleuze
* @author Arjen Poutsma
* @see Jaxb2Decoder
*/
public class Jaxb2Encoder extends AbstractEncoder<Object> {
private final ConcurrentMap<Class<?>, JAXBContext> jaxbContexts = new ConcurrentHashMap<>(64);
private final JaxbContextContainer jaxbContexts = new JaxbContextContainer();
public Jaxb2Encoder() {
super(MimeTypeUtils.APPLICATION_XML, MimeTypeUtils.TEXT_XML);
}
@Override
public boolean canEncode(ResolvableType type, MimeType mimeType, Object... hints) {
if (super.canEncode(type, mimeType, hints)) {
Class<?> outputClass = type.getRawClass();
return outputClass.isAnnotationPresent(XmlRootElement.class) ||
outputClass.isAnnotationPresent(XmlType.class);
}
else {
return false;
}
}
@Override
public Flux<DataBuffer> encode(Publisher<?> inputStream,
DataBufferAllocator allocator, ResolvableType type, MimeType mimeType,
Object... hints) {
return Flux.from(inputStream).map(value -> {
return Flux.from(inputStream).
take(1). // only map 1 value to ensure valid XML output
map(value -> {
try {
DataBuffer buffer = allocator.allocateBuffer(1024);
OutputStream outputStream = buffer.asOutputStream();
Class<?> clazz = ClassUtils.getUserClass(value);
Marshaller marshaller = createMarshaller(clazz);
Marshaller marshaller = jaxbContexts.createMarshaller(clazz);
marshaller.setProperty(Marshaller.JAXB_ENCODING, StandardCharsets.UTF_8.name());
marshaller.marshal(value, outputStream);
return buffer;
@ -75,32 +89,7 @@ public class Jaxb2Encoder extends AbstractEncoder<Object> { @@ -75,32 +89,7 @@ public class Jaxb2Encoder extends AbstractEncoder<Object> {
});
}
protected final Marshaller createMarshaller(Class<?> clazz) {
try {
JAXBContext jaxbContext = getJaxbContext(clazz);
return jaxbContext.createMarshaller();
}
catch (JAXBException ex) {
throw new CodecException("Could not create Marshaller for class " +
"[" + clazz + "]: " + ex.getMessage(), ex);
}
}
protected final JAXBContext getJaxbContext(Class<?> clazz) {
Assert.notNull(clazz, "'clazz' must not be null");
JAXBContext jaxbContext = this.jaxbContexts.get(clazz);
if (jaxbContext == null) {
try {
jaxbContext = JAXBContext.newInstance(clazz);
this.jaxbContexts.putIfAbsent(clazz, jaxbContext);
}
catch (JAXBException ex) {
throw new CodecException("Could not instantiate JAXBContext for class " +
"[" + clazz + "]: " + ex.getMessage(), ex);
}
}
return jaxbContext;
}
}

56
spring-web-reactive/src/main/java/org/springframework/core/codec/support/JaxbContextContainer.java

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.springframework.util.Assert;
/**
* @author Arjen Poutsma
*/
final class JaxbContextContainer {
private final ConcurrentMap<Class<?>, JAXBContext> jaxbContexts =
new ConcurrentHashMap<>(64);
public Marshaller createMarshaller(Class<?> clazz) throws JAXBException {
JAXBContext jaxbContext = getJaxbContext(clazz);
return jaxbContext.createMarshaller();
}
public Unmarshaller createUnmarshaller(Class<?> clazz) throws JAXBException {
JAXBContext jaxbContext = getJaxbContext(clazz);
return jaxbContext.createUnmarshaller();
}
private JAXBContext getJaxbContext(Class<?> clazz) throws JAXBException {
Assert.notNull(clazz, "'clazz' must not be null");
JAXBContext jaxbContext = this.jaxbContexts.get(clazz);
if (jaxbContext == null) {
jaxbContext = JAXBContext.newInstance(clazz);
this.jaxbContexts.putIfAbsent(clazz, jaxbContext);
}
return jaxbContext;
}
}

151
spring-web-reactive/src/main/java/org/springframework/core/codec/support/ListBasedXMLEventReader.java

@ -0,0 +1,151 @@ @@ -0,0 +1,151 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support;
import java.util.List;
import java.util.NoSuchElementException;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* TODO: move to org.springframework.util.xml when merging, hidden behind StaxUtils
*
* @author Arjen Poutsma
*/
class ListBasedXMLEventReader implements XMLEventReader {
private final XMLEvent[] events;
private int cursor = 0;
public ListBasedXMLEventReader(List<XMLEvent> events) {
Assert.notNull(events, "'events' must not be null");
this.events = events.toArray(new XMLEvent[events.size()]);
}
@Override
public boolean hasNext() {
Assert.notNull(events, "'events' must not be null");
return cursor != events.length;
}
@Override
public XMLEvent nextEvent() {
if (cursor < events.length) {
return events[cursor++];
}
else {
throw new NoSuchElementException();
}
}
@Override
public XMLEvent peek() {
if (cursor < events.length) {
return events[cursor];
}
else {
return null;
}
}
@Override
public Object next() {
return nextEvent();
}
/**
* Throws an {@code UnsupportedOperationException} when called.
* @throws UnsupportedOperationException when called
*/
@Override
public void remove() {
throw new UnsupportedOperationException(
"remove not supported on " + ClassUtils.getShortName(getClass()));
}
@Override
public String getElementText() throws XMLStreamException {
if (!peek().isStartElement()) {
throw new XMLStreamException("Not at START_ELEMENT");
}
StringBuilder builder = new StringBuilder();
while (true) {
XMLEvent event = nextEvent();
if (event.isEndElement()) {
break;
}
else if (!event.isCharacters()) {
throw new XMLStreamException(
"Unexpected event [" + event + "] in getElementText()");
}
Characters characters = event.asCharacters();
if (!characters.isIgnorableWhiteSpace()) {
builder.append(event.asCharacters().getData());
}
}
return builder.toString();
}
@Override
public XMLEvent nextTag() throws XMLStreamException {
while (true) {
XMLEvent event = nextEvent();
switch (event.getEventType()) {
case XMLStreamConstants.START_ELEMENT:
case XMLStreamConstants.END_ELEMENT:
return event;
case XMLStreamConstants.END_DOCUMENT:
return null;
case XMLStreamConstants.SPACE:
case XMLStreamConstants.COMMENT:
case XMLStreamConstants.PROCESSING_INSTRUCTION:
continue;
case XMLStreamConstants.CDATA:
case XMLStreamConstants.CHARACTERS:
if (!event.asCharacters().isWhiteSpace()) {
throw new XMLStreamException(
"Non-ignorable whitespace CDATA or CHARACTERS event in nextTag()");
}
break;
default:
throw new XMLStreamException("Received event [" + event +
"], instead of START_ELEMENT or END_ELEMENT.");
}
}
}
/**
* Throws an {@code IllegalArgumentException} when called.
* @throws IllegalArgumentException when called.
*/
@Override
public Object getProperty(String name) throws IllegalArgumentException {
throw new IllegalArgumentException("Property not supported: [" + name + "]");
}
@Override
public void close() {
}
}

145
spring-web-reactive/src/main/java/org/springframework/core/codec/support/XmlEventDecoder.java

@ -0,0 +1,145 @@ @@ -0,0 +1,145 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.XMLEventAllocator;
import com.fasterxml.aalto.AsyncByteBufferFeeder;
import com.fasterxml.aalto.AsyncXMLInputFactory;
import com.fasterxml.aalto.AsyncXMLStreamReader;
import com.fasterxml.aalto.evt.EventAllocatorImpl;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.support.DataBufferUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
/**
* Decodes a {@link DataBuffer} stream into a stream of {@link XMLEvent}s. That is, given
* the following XML:
* <pre>{@code
* <root>
* <child>foo</child>
* <child>bar</child>
* </root>}
* </pre>
* this method with result in a flux with the following events:
* <ol>
* <li>{@link javax.xml.stream.events.StartDocument}</li>
* <li>{@link javax.xml.stream.events.StartElement} {@code root}</li>
* <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>
* <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>
* <li>{@link javax.xml.stream.events.EndElement} {@code root}</li>
* </ol>
*
* Note that this decoder is not registered by default, but used internally by other
* decoders who are.
*
* @author Arjen Poutsma
*/
public class XmlEventDecoder extends AbstractDecoder<XMLEvent> {
private static final boolean aaltoPresent = ClassUtils
.isPresent("com.fasterxml.aalto.AsyncXMLStreamReader",
XmlEventDecoder.class.getClassLoader());
private static final XMLInputFactory inputFactory = XMLInputFactory.newFactory();
public XmlEventDecoder() {
super(MimeTypeUtils.APPLICATION_XML, MimeTypeUtils.TEXT_XML);
}
@Override
public Flux<XMLEvent> decode(Publisher<DataBuffer> inputStream, ResolvableType type,
MimeType mimeType, Object... hints) {
if (aaltoPresent) {
return Flux.from(inputStream).flatMap(new AaltoDataBufferToXmlEvent());
}
else {
try {
InputStream blockingStream = DataBufferUtils.toInputStream(inputStream);
XMLEventReader eventReader =
inputFactory.createXMLEventReader(blockingStream);
return Flux.fromIterable((Iterable<XMLEvent>) () -> eventReader);
}
catch (XMLStreamException ex) {
return Flux.error(ex);
}
}
}
/*
* Separate static class to isolate Aalto dependency.
*/
private static class AaltoDataBufferToXmlEvent
implements Function<DataBuffer, Publisher<? extends XMLEvent>> {
private static final AsyncXMLInputFactory inputFactory =
(AsyncXMLInputFactory) XmlEventDecoder.inputFactory;
private final AsyncXMLStreamReader<AsyncByteBufferFeeder> streamReader =
inputFactory.createAsyncForByteBuffer();
private final XMLEventAllocator eventAllocator =
EventAllocatorImpl.getDefaultInstance();
@Override
public Publisher<? extends XMLEvent> apply(DataBuffer dataBuffer) {
try {
streamReader.getInputFeeder().feedInput(dataBuffer.asByteBuffer());
List<XMLEvent> events = new ArrayList<>();
while (true) {
if (streamReader.next() == AsyncXMLStreamReader.EVENT_INCOMPLETE) {
// no more events with what currently has been fed to the reader
break;
}
else {
XMLEvent event = eventAllocator.allocate(streamReader);
events.add(event);
if (event.isEndDocument()) {
break;
}
}
}
return Flux.fromIterable(events);
}
catch (XMLStreamException ex) {
return Mono.error(ex);
}
}
}
}

3
spring-web-reactive/src/main/java/org/springframework/web/reactive/method/annotation/RequestMappingHandlerAdapter.java

@ -31,6 +31,7 @@ import org.springframework.beans.factory.InitializingBean; @@ -31,6 +31,7 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.codec.Decoder;
import org.springframework.core.codec.support.ByteBufferDecoder;
import org.springframework.core.codec.support.JacksonJsonDecoder;
import org.springframework.core.codec.support.Jaxb2Decoder;
import org.springframework.core.codec.support.JsonObjectDecoder;
import org.springframework.core.codec.support.StringDecoder;
import org.springframework.core.convert.ConversionService;
@ -100,7 +101,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin @@ -100,7 +101,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, Initializin
if (ObjectUtils.isEmpty(this.argumentResolvers)) {
List<Decoder<?>> decoders = Arrays.asList(new ByteBufferDecoder(),
new StringDecoder(),
new StringDecoder(), new Jaxb2Decoder(),
new JacksonJsonDecoder(new JsonObjectDecoder()));
this.argumentResolvers.add(new RequestParamArgumentResolver());

252
spring-web-reactive/src/test/java/org/springframework/core/codec/support/Jaxb2DecoderTests.java

@ -16,38 +16,270 @@ @@ -16,38 +16,270 @@
package org.springframework.core.codec.support;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.stream.events.XMLEvent;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.test.TestSubscriber;
import org.springframework.core.ResolvableType;
import org.springframework.core.codec.support.jaxb.XmlRootElement;
import org.springframework.core.codec.support.jaxb.XmlRootElementWithName;
import org.springframework.core.codec.support.jaxb.XmlRootElementWithNameAndNamespace;
import org.springframework.core.codec.support.jaxb.XmlType;
import org.springframework.core.codec.support.jaxb.XmlTypeWithName;
import org.springframework.core.codec.support.jaxb.XmlTypeWithNameAndNamespace;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import static org.junit.Assert.*;
import reactor.core.test.TestSubscriber;
/**
* @author Sebastien Deleuze
*/
public class Jaxb2DecoderTests extends AbstractAllocatingTestCase {
private static final String POJO_ROOT = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<pojo>" +
"<foo>foofoo</foo>" +
"<bar>barbar</bar>" +
"</pojo>";
private static final String POJO_CHILD =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<root>" +
"<pojo>" +
"<foo>foo</foo>" +
"<bar>bar</bar>" +
"</pojo>" +
"<pojo>" +
"<foo>foofoo</foo>" +
"<bar>barbar</bar>" +
"</pojo>" +
"<root/>";
private final Jaxb2Decoder decoder = new Jaxb2Decoder();
private final XmlEventDecoder xmlEventDecoder = new XmlEventDecoder();
@Test
public void canDecode() {
assertTrue(decoder.canDecode(null, MediaType.APPLICATION_XML));
assertTrue(decoder.canDecode(null, MediaType.TEXT_XML));
assertFalse(decoder.canDecode(null, MediaType.APPLICATION_JSON));
assertTrue(decoder.canDecode(ResolvableType.forClass(Pojo.class),
MediaType.APPLICATION_XML));
assertTrue(decoder.canDecode(ResolvableType.forClass(Pojo.class),
MediaType.TEXT_XML));
assertFalse(decoder.canDecode(ResolvableType.forClass(Pojo.class),
MediaType.APPLICATION_JSON));
assertTrue(decoder.canDecode(ResolvableType.forClass(TypePojo.class),
MediaType.APPLICATION_XML));
assertFalse(decoder.canDecode(ResolvableType.forClass(getClass()),
MediaType.APPLICATION_XML));
}
@Test
public void splitOneBranches() {
Flux<XMLEvent> xmlEvents =
xmlEventDecoder.decode(Flux.just(stringBuffer(POJO_ROOT)), null, null);
Flux<List<XMLEvent>> result = decoder.split(xmlEvents, new QName("pojo"));
TestSubscriber<List<XMLEvent>> resultSubscriber = new TestSubscriber<>();
resultSubscriber.bindTo(result).
assertNoError().
assertComplete().
assertValuesWith(events -> {
assertEquals(8, events.size());
assertStartElement(events.get(0), "pojo");
assertStartElement(events.get(1), "foo");
assertCharacters(events.get(2), "foofoo");
assertEndElement(events.get(3), "foo");
assertStartElement(events.get(4), "bar");
assertCharacters(events.get(5), "barbar");
assertEndElement(events.get(6), "bar");
assertEndElement(events.get(7), "pojo");
});
}
@Test
public void splitMultipleBranches() {
Flux<XMLEvent> xmlEvents =
xmlEventDecoder.decode(Flux.just(stringBuffer(POJO_CHILD)), null, null);
Flux<List<XMLEvent>> result = decoder.split(xmlEvents, new QName("pojo"));
TestSubscriber<List<XMLEvent>> resultSubscriber = new TestSubscriber<>();
resultSubscriber.bindTo(result).
assertNoError().
assertComplete().
assertValuesWith(events -> {
assertEquals(8, events.size());
assertStartElement(events.get(0), "pojo");
assertStartElement(events.get(1), "foo");
assertCharacters(events.get(2), "foo");
assertEndElement(events.get(3), "foo");
assertStartElement(events.get(4), "bar");
assertCharacters(events.get(5), "bar");
assertEndElement(events.get(6), "bar");
assertEndElement(events.get(7), "pojo");
}, events -> {
assertEquals(8, events.size());
assertStartElement(events.get(0), "pojo");
assertStartElement(events.get(1), "foo");
assertCharacters(events.get(2), "foofoo");
assertEndElement(events.get(3), "foo");
assertStartElement(events.get(4), "bar");
assertCharacters(events.get(5), "barbar");
assertEndElement(events.get(6), "bar");
assertEndElement(events.get(7), "pojo");
});
}
private static void assertStartElement(XMLEvent event, String expectedLocalName) {
assertTrue(event.isStartElement());
assertEquals(expectedLocalName, event.asStartElement().getName().getLocalPart());
}
private static void assertEndElement(XMLEvent event, String expectedLocalName) {
assertTrue(event.isEndElement());
assertEquals(expectedLocalName, event.asEndElement().getName().getLocalPart());
}
private static void assertCharacters(XMLEvent event, String expectedData) {
assertTrue(event.isCharacters());
assertEquals(expectedData, event.asCharacters().getData());
}
@Test
public void decodeSingleXmlRootElement() throws Exception {
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_ROOT));
Flux<Object> output =
decoder.decode(source, ResolvableType.forClass(Pojo.class), null);
TestSubscriber<Object> testSubscriber = new TestSubscriber<>();
testSubscriber.bindTo(output).
assertNoError().
assertComplete().
assertValues(new Pojo("foofoo", "barbar")
);
}
@Test
public void decodeSingleXmlTypeElement() throws Exception {
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_ROOT));
Flux<Object> output =
decoder.decode(source, ResolvableType.forClass(TypePojo.class), null);
TestSubscriber<Object> testSubscriber = new TestSubscriber<>();
testSubscriber.bindTo(output).
assertNoError().
assertComplete().
assertValues(new TypePojo("foofoo", "barbar")
);
}
@Test
public void decode() {
Flux<DataBuffer> source = Flux.just(stringBuffer(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><pojo><bar>barbar</bar><foo>foofoo</foo></pojo>"));
Flux<Object> output = decoder.decode(source, ResolvableType.forClass(Pojo.class), null);
public void decodeMultipleXmlRootElement() throws Exception {
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_CHILD));
Flux<Object> output =
decoder.decode(source, ResolvableType.forClass(Pojo.class), null);
TestSubscriber<Object> testSubscriber = new TestSubscriber<>();
testSubscriber.bindTo(output)
.assertValues(new Pojo("foofoo", "barbar"));
testSubscriber.bindTo(output).
assertNoError().
assertComplete().
assertValues(new Pojo("foo", "bar"), new Pojo("foofoo", "barbar")
);
}
@Test
public void decodeMultipleXmlTypeElement() throws Exception {
Flux<DataBuffer> source = Flux.just(stringBuffer(POJO_CHILD));
Flux<Object> output =
decoder.decode(source, ResolvableType.forClass(TypePojo.class), null);
TestSubscriber<Object> testSubscriber = new TestSubscriber<>();
testSubscriber.bindTo(output).
assertNoError().
assertComplete().
assertValues(new TypePojo("foo", "bar"), new TypePojo("foofoo", "barbar")
);
}
@Test
public void toExpectedQName() {
assertEquals(new QName("pojo"), decoder.toQName(Pojo.class));
assertEquals(new QName("pojo"), decoder.toQName(TypePojo.class));
assertEquals(new QName("namespace", "name"),
decoder.toQName(XmlRootElementWithNameAndNamespace.class));
assertEquals(new QName("namespace", "name"),
decoder.toQName(XmlRootElementWithName.class));
assertEquals(new QName("namespace", "xmlRootElement"),
decoder.toQName(XmlRootElement.class));
assertEquals(new QName("namespace", "name"),
decoder.toQName(XmlTypeWithNameAndNamespace.class));
assertEquals(new QName("namespace", "name"),
decoder.toQName(XmlTypeWithName.class));
assertEquals(new QName("namespace", "xmlType"), decoder.toQName(XmlType.class));
}
@javax.xml.bind.annotation.XmlType(name = "pojo")
public static 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(Object o) {
if (this == o) {
return true;
}
if (o instanceof TypePojo) {
TypePojo other = (TypePojo) o;
return this.foo.equals(other.foo) && this.bar.equals(other.bar);
}
return false;
}
}
}

43
spring-web-reactive/src/test/java/org/springframework/core/codec/support/Jaxb2EncoderTests.java

@ -16,20 +16,27 @@ @@ -16,20 +16,27 @@
package org.springframework.core.codec.support;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.junit.Before;
import org.junit.Test;
import org.xml.sax.SAXException;
import reactor.core.publisher.Flux;
import reactor.core.test.TestSubscriber;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
import org.springframework.http.MediaType;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import static org.custommonkey.xmlunit.XMLAssert.fail;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author Sebastien Deleuze
* @author Arjen Poutsma
*/
public class Jaxb2EncoderTests extends AbstractAllocatingTestCase {
@ -42,23 +49,37 @@ public class Jaxb2EncoderTests extends AbstractAllocatingTestCase { @@ -42,23 +49,37 @@ public class Jaxb2EncoderTests extends AbstractAllocatingTestCase {
@Test
public void canEncode() {
assertTrue(encoder.canEncode(null, MediaType.APPLICATION_XML));
assertTrue(encoder.canEncode(null, MediaType.TEXT_XML));
assertFalse(encoder.canEncode(null, MediaType.APPLICATION_JSON));
assertTrue(encoder.canEncode(ResolvableType.forClass(Pojo.class),
MediaType.APPLICATION_XML));
assertTrue(encoder.canEncode(ResolvableType.forClass(Pojo.class),
MediaType.TEXT_XML));
assertFalse(encoder.canEncode(ResolvableType.forClass(Pojo.class),
MediaType.APPLICATION_JSON));
assertTrue(encoder.canEncode(
ResolvableType.forClass(Jaxb2DecoderTests.TypePojo.class),
MediaType.APPLICATION_XML));
assertFalse(encoder.canEncode(ResolvableType.forClass(getClass()),
MediaType.APPLICATION_XML));
}
@Test
public void encode() {
Flux<Pojo> source = Flux.just(new Pojo("foofoo", "barbar"), new Pojo("foofoofoo", "barbarbar"));
Flux<String> output = encoder.encode(source, allocator, null, null).map(chunk -> {
byte[] b = new byte[chunk.readableByteCount()];
chunk.read(b);
return new String(b, StandardCharsets.UTF_8);
});
Flux<String> output =
encoder.encode(source, allocator, ResolvableType.forClass(Pojo.class),
MediaType.APPLICATION_XML).map(chunk -> DataBufferTestUtils
.dumpString(chunk, StandardCharsets.UTF_8));
TestSubscriber<String> testSubscriber = new TestSubscriber<>();
testSubscriber.bindTo(output)
.assertValues("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><pojo><bar>barbar</bar><foo>foofoo</foo></pojo>",
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><pojo><bar>barbarbar</bar><foo>foofoofoo</foo></pojo>");
testSubscriber.bindTo(output).assertValuesWith(s -> {
try {
assertXMLEqual("<pojo><bar>barbar</bar><foo>foofoo</foo></pojo>", s);
}
catch (SAXException | IOException e) {
fail(e.getMessage());
}
});
}
}

77
spring-web-reactive/src/test/java/org/springframework/core/codec/support/XmlEventDecoderTests.java

@ -0,0 +1,77 @@ @@ -0,0 +1,77 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support;
import javax.xml.stream.events.XMLEvent;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.test.TestSubscriber;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma
*/
public class XmlEventDecoderTests extends AbstractAllocatingTestCase {
private static final String XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<pojo>" +
"<foo>foofoo</foo>" +
"<bar>barbar</bar>" +
"</pojo>";
private XmlEventDecoder decoder = new XmlEventDecoder();
@Test
public void toXMLEvents() {
Flux<XMLEvent> events = decoder.decode(Flux.just(stringBuffer(XML)), null, null);
TestSubscriber<XMLEvent> testSubscriber = new TestSubscriber<>();
testSubscriber.bindTo(events).
assertNoError().
assertComplete().
assertValuesWith(e -> assertTrue(e.isStartDocument()),
e -> assertStartElement(e, "pojo"),
e -> assertStartElement(e, "foo"),
e -> assertCharacters(e, "foofoo"),
e -> assertEndElement(e, "foo"),
e -> assertStartElement(e, "bar"),
e -> assertCharacters(e, "barbar"),
e -> assertEndElement(e, "bar"),
e -> assertEndElement(e, "pojo"));
}
private static void assertStartElement(XMLEvent event, String expectedLocalName) {
assertTrue(event.isStartElement());
assertEquals(expectedLocalName, event.asStartElement().getName().getLocalPart());
}
private static void assertEndElement(XMLEvent event, String expectedLocalName) {
assertTrue(event + " is no end element", event.isEndElement());
assertEquals(expectedLocalName, event.asEndElement().getName().getLocalPart());
}
private static void assertCharacters(XMLEvent event, String expectedData) {
assertTrue(event.isCharacters());
assertEquals(expectedData, event.asCharacters().getData());
}
}

25
spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlRootElement.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support.jaxb;
/**
* @author Arjen Poutsma
*/
@javax.xml.bind.annotation.XmlRootElement
public class XmlRootElement {
}

27
spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlRootElementWithName.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support.jaxb;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @author Arjen Poutsma
*/
@XmlRootElement(name = "name")
public class XmlRootElementWithName {
}

27
spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlRootElementWithNameAndNamespace.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support.jaxb;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @author Arjen Poutsma
*/
@XmlRootElement(name = "name", namespace = "namespace")
public class XmlRootElementWithNameAndNamespace {
}

25
spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlType.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support.jaxb;
/**
* @author Arjen Poutsma
*/
@javax.xml.bind.annotation.XmlType
public class XmlType {
}

27
spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlTypeWithName.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support.jaxb;
import javax.xml.bind.annotation.XmlType;
/**
* @author Arjen Poutsma
*/
@XmlType(name = "name")
public class XmlTypeWithName {
}

27
spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/XmlTypeWithNameAndNamespace.java

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
/*
* Copyright 2002-2016 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
*
* http://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.core.codec.support.jaxb;
import javax.xml.bind.annotation.XmlType;
/**
* @author Arjen Poutsma
*/
@XmlType(name = "name", namespace = "namespace")
public class XmlTypeWithNameAndNamespace {
}

18
spring-web-reactive/src/test/java/org/springframework/core/codec/support/jaxb/package-info.java

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
/*
* Copyright 2002-2016 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
*
* http://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.
*/
@javax.xml.bind.annotation.XmlSchema(namespace = "namespace")
package org.springframework.core.codec.support.jaxb;

56
spring-web-reactive/src/test/java/org/springframework/web/reactive/method/annotation/RequestMappingIntegrationTests.java

@ -24,6 +24,8 @@ import java.util.Arrays; @@ -24,6 +24,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.junit.Ignore;
import org.junit.Test;
@ -114,7 +116,8 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati @@ -114,7 +116,8 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
RestTemplate restTemplate = new RestTemplate();
URI url = new URI("http://localhost:" + port + "/raw");
RequestEntity<Void> request = RequestEntity.get(url).build();
RequestEntity<Void> request =
RequestEntity.get(url).accept(MediaType.APPLICATION_JSON).build();
Person person = restTemplate.exchange(request, Person.class).getBody();
assertEquals(new Person("Robert"), person);
@ -262,17 +265,32 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati @@ -262,17 +265,32 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
@Test
public void publisherCreate() throws Exception {
create("http://localhost:" + this.port + "/publisher-create");
createJson("http://localhost:" + this.port + "/publisher-create");
}
@Test
public void publisherCreateXml() throws Exception {
createXml("http://localhost:" + this.port + "/publisher-create");
}
@Test
public void fluxCreate() throws Exception {
create("http://localhost:" + this.port + "/flux-create");
createJson("http://localhost:" + this.port + "/flux-create");
}
@Test
public void fluxCreateXml() throws Exception {
createXml("http://localhost:" + this.port + "/flux-create");
}
@Test
public void observableCreate() throws Exception {
create("http://localhost:" + this.port + "/observable-create");
createJson("http://localhost:" + this.port + "/observable-create");
}
@Test
public void observableCreateXml() throws Exception {
createXml("http://localhost:" + this.port + "/observable-create");
}
@Test
@ -337,7 +355,7 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati @@ -337,7 +355,7 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
assertEquals("MARIE", results.get(1).getName());
}
private void create(String requestUrl) throws Exception {
private void createJson(String requestUrl) throws Exception {
RestTemplate restTemplate = new RestTemplate();
URI url = new URI(requestUrl);
RequestEntity<List<Person>> request = RequestEntity.post(url)
@ -349,6 +367,21 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati @@ -349,6 +367,21 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
assertEquals(2, this.wac.getBean(TestRestController.class).persons.size());
}
private void createXml(String requestUrl) throws Exception {
RestTemplate restTemplate = new RestTemplate();
URI url = new URI(requestUrl);
People people = new People();
people.getPerson().add(new Person("Robert"));
people.getPerson().add(new Person("Marie"));
RequestEntity<People> request =
RequestEntity.post(url).contentType(MediaType.APPLICATION_XML)
.body(people);
ResponseEntity<Void> response = restTemplate.exchange(request, Void.class);
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(2, this.wac.getBean(TestRestController.class).persons.size());
}
@Configuration
@SuppressWarnings("unused")
@ -609,6 +642,7 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati @@ -609,6 +642,7 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
}
@XmlRootElement
private static class Person {
private String name;
@ -654,4 +688,16 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati @@ -654,4 +688,16 @@ public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrati
}
}
@XmlRootElement
private static class People {
private List<Person> persons = new ArrayList<>();
@XmlElement
public List<Person> getPerson() {
return this.persons;
}
}
}

Loading…
Cancel
Save