/*
* Copyright 2002-2018 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.http.converter.xml;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.xml.StaxUtils;
An HttpMessageConverter
that can read XML collections using JAXB2. This converter can read collections that contain classes annotated with XmlRootElement
and XmlType
. Note that this converter does not support writing.
Author: Arjen Poutsma, Rossen Stoyanchev Type parameters: - <T> – the converted object type
Since: 3.2
/**
* An {@code HttpMessageConverter} that can read XML collections using JAXB2.
*
* <p>This converter can read {@linkplain Collection collections} that contain classes
* annotated with {@link XmlRootElement} and {@link XmlType}. Note that this converter
* does not support writing.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.2
* @param <T> the converted object type
*/
@SuppressWarnings("rawtypes")
public class Jaxb2CollectionHttpMessageConverter<T extends Collection>
extends AbstractJaxb2HttpMessageConverter<T> implements GenericHttpMessageConverter<T> {
private final XMLInputFactory inputFactory = createXmlInputFactory();
Always returns false
since Jaxb2CollectionHttpMessageConverter required generic type information in order to read a Collection. /**
* Always returns {@code false} since Jaxb2CollectionHttpMessageConverter
* required generic type information in order to read a Collection.
*/
@Override
public boolean canRead(Class<?> clazz, @Nullable MediaType mediaType) {
return false;
}
{@inheritDoc}
Jaxb2CollectionHttpMessageConverter can read a generic Collection
where the generic type is a JAXB type annotated with XmlRootElement
or XmlType
.
/**
* {@inheritDoc}
* <p>Jaxb2CollectionHttpMessageConverter can read a generic
* {@link Collection} where the generic type is a JAXB type annotated with
* {@link XmlRootElement} or {@link XmlType}.
*/
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
if (!(type instanceof ParameterizedType)) {
return false;
}
ParameterizedType parameterizedType = (ParameterizedType) type;
if (!(parameterizedType.getRawType() instanceof Class)) {
return false;
}
Class<?> rawType = (Class<?>) parameterizedType.getRawType();
if (!(Collection.class.isAssignableFrom(rawType))) {
return false;
}
if (parameterizedType.getActualTypeArguments().length != 1) {
return false;
}
Type typeArgument = parameterizedType.getActualTypeArguments()[0];
if (!(typeArgument instanceof Class)) {
return false;
}
Class<?> typeArgumentClass = (Class<?>) typeArgument;
return (typeArgumentClass.isAnnotationPresent(XmlRootElement.class) ||
typeArgumentClass.isAnnotationPresent(XmlType.class)) && canRead(mediaType);
}
Always returns false
since Jaxb2CollectionHttpMessageConverter does not convert collections to XML. /**
* Always returns {@code false} since Jaxb2CollectionHttpMessageConverter
* does not convert collections to XML.
*/
@Override
public boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType) {
return false;
}
Always returns false
since Jaxb2CollectionHttpMessageConverter does not convert collections to XML. /**
* Always returns {@code false} since Jaxb2CollectionHttpMessageConverter
* does not convert collections to XML.
*/
@Override
public boolean canWrite(@Nullable Type type, @Nullable Class<?> clazz, @Nullable MediaType mediaType) {
return false;
}
@Override
protected boolean supports(Class<?> clazz) {
// should not be called, since we override canRead/Write
throw new UnsupportedOperationException();
}
@Override
protected T readFromSource(Class<? extends T> clazz, HttpHeaders headers, Source source) throws Exception {
// should not be called, since we return false for canRead(Class)
throw new UnsupportedOperationException();
}
@Override
@SuppressWarnings("unchecked")
public T read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
ParameterizedType parameterizedType = (ParameterizedType) type;
T result = createCollection((Class<?>) parameterizedType.getRawType());
Class<?> elementClass = (Class<?>) parameterizedType.getActualTypeArguments()[0];
try {
Unmarshaller unmarshaller = createUnmarshaller(elementClass);
XMLStreamReader streamReader = this.inputFactory.createXMLStreamReader(inputMessage.getBody());
int event = moveToFirstChildOfRootElement(streamReader);
while (event != XMLStreamReader.END_DOCUMENT) {
if (elementClass.isAnnotationPresent(XmlRootElement.class)) {
result.add(unmarshaller.unmarshal(streamReader));
}
else if (elementClass.isAnnotationPresent(XmlType.class)) {
result.add(unmarshaller.unmarshal(streamReader, elementClass).getValue());
}
else {
// should not happen, since we check in canRead(Type)
throw new HttpMessageNotReadableException(
"Cannot unmarshal to [" + elementClass + "]", inputMessage);
}
event = moveToNextElement(streamReader);
}
return result;
}
catch (XMLStreamException ex) {
throw new HttpMessageNotReadableException(
"Failed to read XML stream: " + ex.getMessage(), ex, inputMessage);
}
catch (UnmarshalException ex) {
throw new HttpMessageNotReadableException(
"Could not unmarshal to [" + elementClass + "]: " + ex.getMessage(), ex, inputMessage);
}
catch (JAXBException ex) {
throw new HttpMessageConversionException("Invalid JAXB setup: " + ex.getMessage(), ex);
}
}
Create a Collection of the given type, with the given initial capacity
(if supported by the Collection type).
Params: - collectionClass – the type of Collection to instantiate
Returns: the created Collection instance
/**
* Create a Collection of the given type, with the given initial capacity
* (if supported by the Collection type).
* @param collectionClass the type of Collection to instantiate
* @return the created Collection instance
*/
@SuppressWarnings("unchecked")
protected T createCollection(Class<?> collectionClass) {
if (!collectionClass.isInterface()) {
try {
return (T) ReflectionUtils.accessibleConstructor(collectionClass).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Could not instantiate collection class: " + collectionClass.getName(), ex);
}
}
else if (List.class == collectionClass) {
return (T) new ArrayList();
}
else if (SortedSet.class == collectionClass) {
return (T) new TreeSet();
}
else {
return (T) new LinkedHashSet();
}
}
private int moveToFirstChildOfRootElement(XMLStreamReader streamReader) throws XMLStreamException {
// root
int event = streamReader.next();
while (event != XMLStreamReader.START_ELEMENT) {
event = streamReader.next();
}
// first child
event = streamReader.next();
while ((event != XMLStreamReader.START_ELEMENT) && (event != XMLStreamReader.END_DOCUMENT)) {
event = streamReader.next();
}
return event;
}
private int moveToNextElement(XMLStreamReader streamReader) throws XMLStreamException {
int event = streamReader.getEventType();
while (event != XMLStreamReader.START_ELEMENT && event != XMLStreamReader.END_DOCUMENT) {
event = streamReader.next();
}
return event;
}
@Override
public void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
throw new UnsupportedOperationException();
}
@Override
protected void writeToResult(T t, HttpHeaders headers, Result result) throws Exception {
throw new UnsupportedOperationException();
}
Create an XMLInputFactory
that this converter will use to create XMLStreamReader
and XMLEventReader
objects. Can be overridden in subclasses, adding further initialization of the factory.
The resulting factory is cached, so this method will only be called once.
See Also:
/**
* Create an {@code XMLInputFactory} that this converter will use to create
* {@link javax.xml.stream.XMLStreamReader} and {@link javax.xml.stream.XMLEventReader}
* objects.
* <p>Can be overridden in subclasses, adding further initialization of the factory.
* The resulting factory is cached, so this method will only be called once.
* @see StaxUtils#createDefensiveInputFactory()
*/
protected XMLInputFactory createXmlInputFactory() {
return StaxUtils.createDefensiveInputFactory();
}
}