Browse Source
This change makes it possible to use the RestTemplate to read an HTTP response into a target generic type object. The RestTemplate has three new exchange(...) methods that accept ParameterizedTypeReference -- a new class that enables capturing and passing generic type info. See the Javadoc of the three new methods in RestOperations for a short example. To support this feature, the HttpMessageConverter is now extended by GenericHttpMessageConverter, which adds a method for reading an HttpInputMessage to a specific generic type. The new interface is implemented by the MappingJacksonHttpMessageConverter and also by a new Jaxb2CollectionHttpMessageConverter that can read read a generic Collection where the generic type is a JAXB type annotated with @XmlRootElement or @XmlType. Issue: SPR-7023pull/120/merge
14 changed files with 1213 additions and 99 deletions
@ -0,0 +1,99 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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; |
||||||
|
|
||||||
|
import java.lang.reflect.ParameterizedType; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
|
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* The purpose of this class is to enable capturing and passing a generic |
||||||
|
* {@link Type}. In order to capture the generic type and retain it at runtime, |
||||||
|
* you need to create a sub-class as follows: |
||||||
|
* |
||||||
|
* <pre class="code"> |
||||||
|
* ParameterizedTypeReference<List<String>> typeRef = new ParameterizedTypeReference<List<String>>() {}; |
||||||
|
* </pre> |
||||||
|
* |
||||||
|
* <p>The resulting {@code typeReference} instance can then be used to obtain a |
||||||
|
* {@link Type} instance that carries parameterized type information. |
||||||
|
* For more information on "super type tokens" see the link to Neal Gafter's blog post. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 3.2 |
||||||
|
* |
||||||
|
* @see http://gafter.blogspot.nl/2006/12/super-type-tokens.html
|
||||||
|
*/ |
||||||
|
public abstract class ParameterizedTypeReference<T> { |
||||||
|
|
||||||
|
private final Type type; |
||||||
|
|
||||||
|
protected ParameterizedTypeReference() { |
||||||
|
Class<?> parameterizedTypeReferenceSubClass = findParameterizedTypeReferenceSubClass(getClass()); |
||||||
|
|
||||||
|
Type type = parameterizedTypeReferenceSubClass.getGenericSuperclass(); |
||||||
|
Assert.isInstanceOf(ParameterizedType.class, type); |
||||||
|
|
||||||
|
ParameterizedType parameterizedType = (ParameterizedType) type; |
||||||
|
Assert.isTrue(parameterizedType.getActualTypeArguments().length == 1); |
||||||
|
|
||||||
|
this.type = parameterizedType.getActualTypeArguments()[0]; |
||||||
|
} |
||||||
|
|
||||||
|
private static Class<?> findParameterizedTypeReferenceSubClass(Class<?> child) { |
||||||
|
|
||||||
|
Class<?> parent = child.getSuperclass(); |
||||||
|
|
||||||
|
if (Object.class.equals(parent)) { |
||||||
|
throw new IllegalStateException("Expected ParameterizedTypeReference superclass"); |
||||||
|
} |
||||||
|
else if (ParameterizedTypeReference.class.equals(parent)) { |
||||||
|
return child; |
||||||
|
} |
||||||
|
else { |
||||||
|
return findParameterizedTypeReferenceSubClass(parent); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public Type getType() { |
||||||
|
return this.type; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object o) { |
||||||
|
if (this == o) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (o instanceof ParameterizedTypeReference) { |
||||||
|
ParameterizedTypeReference<?> other = (ParameterizedTypeReference<?>) o; |
||||||
|
return this.type.equals(other.type); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return this.type.hashCode(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return "ParameterizedTypeReference<" + this.type + ">"; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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; |
||||||
|
|
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test fixture for {@link ParameterizedTypeReference}. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
*/ |
||||||
|
public class ParameterizedTypeReferenceTest { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void map() throws NoSuchMethodException { |
||||||
|
Type mapType = getClass().getMethod("mapMethod").getGenericReturnType(); |
||||||
|
ParameterizedTypeReference<Map<Object,String>> mapTypeReference = new ParameterizedTypeReference<Map<Object,String>>() {}; |
||||||
|
assertEquals(mapType, mapTypeReference.getType()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void list() throws NoSuchMethodException { |
||||||
|
Type mapType = getClass().getMethod("listMethod").getGenericReturnType(); |
||||||
|
ParameterizedTypeReference<List<String>> mapTypeReference = new ParameterizedTypeReference<List<String>>() {}; |
||||||
|
assertEquals(mapType, mapTypeReference.getType()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void string() { |
||||||
|
ParameterizedTypeReference<String> typeReference = new ParameterizedTypeReference<String>() {}; |
||||||
|
assertEquals(String.class, typeReference.getType()); |
||||||
|
} |
||||||
|
|
||||||
|
public static Map<Object, String> mapMethod() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
public static List<String> listMethod() { |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,60 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
|
||||||
|
import org.springframework.core.ParameterizedTypeReference; |
||||||
|
import org.springframework.http.HttpInputMessage; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
|
||||||
|
/** |
||||||
|
* A specialization of {@link HttpMessageConverter} that can convert an HTTP |
||||||
|
* request into a target object of a specified generic type. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
* @since 3.2 |
||||||
|
* |
||||||
|
* @see ParameterizedTypeReference |
||||||
|
*/ |
||||||
|
public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> { |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates whether the given type can be read by this converter. |
||||||
|
* @param type the type to test for readability |
||||||
|
* @param mediaType the media type to read, can be {@code null} if not specified. |
||||||
|
* Typically the value of a {@code Content-Type} header. |
||||||
|
* @return {@code true} if readable; {@code false} otherwise |
||||||
|
*/ |
||||||
|
boolean canRead(Type type, MediaType mediaType); |
||||||
|
|
||||||
|
/** |
||||||
|
* Read an object of the given type form the given input message, and returns it. |
||||||
|
* @param clazz the type of object to return. This type must have previously |
||||||
|
* been passed to the {@link #canRead canRead} method of this interface, |
||||||
|
* which must have returned {@code true}. |
||||||
|
* @param type the type of the target object |
||||||
|
* @param inputMessage the HTTP input message to read from |
||||||
|
* @return the converted object |
||||||
|
* @throws IOException in case of I/O errors |
||||||
|
* @throws HttpMessageNotReadableException in case of conversion errors |
||||||
|
*/ |
||||||
|
T read(Type type, HttpInputMessage inputMessage) |
||||||
|
throws IOException, HttpMessageNotReadableException; |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,228 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.MediaType; |
||||||
|
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||||
|
import org.springframework.http.converter.HttpMessageConversionException; |
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 |
||||||
|
* @since 3.2 |
||||||
|
*/ |
||||||
|
public class Jaxb2CollectionHttpMessageConverter<T extends Collection> |
||||||
|
extends AbstractJaxb2HttpMessageConverter<T> implements GenericHttpMessageConverter<T> { |
||||||
|
|
||||||
|
private final XMLInputFactory inputFactory = createXmlInputFactory(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Always returns {@code false} since Jaxb2CollectionHttpMessageConverter |
||||||
|
* required generic type information in order to read a Collection. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public boolean canRead(Class<?> clazz, MediaType mediaType) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* {@inheritDoc} |
||||||
|
* <p>Jaxb2CollectionHttpMessageConverter can read a generic |
||||||
|
* {@link Collection} where the generic type is a JAXB type annotated with |
||||||
|
* {@link XmlRootElement} or {@link XmlType}. |
||||||
|
*/ |
||||||
|
public boolean canRead(Type type, 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 {@code false} since Jaxb2CollectionHttpMessageConverter |
||||||
|
* does not convert collections to XML. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public boolean canWrite(Class<?> clazz, 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 IOException { |
||||||
|
// should not be called, since we return false for canRead(Class)
|
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
public T read(Type type, 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 HttpMessageConversionException("Could not unmarshal to [" + elementClass + "]"); |
||||||
|
} |
||||||
|
event = moveToNextElement(streamReader); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
catch (UnmarshalException ex) { |
||||||
|
throw new HttpMessageNotReadableException("Could not unmarshal to [" + elementClass + "]: " + ex.getMessage(), ex); |
||||||
|
} |
||||||
|
catch (JAXBException ex) { |
||||||
|
throw new HttpMessageConversionException("Could not instantiate JAXBContext: " + ex.getMessage(), ex); |
||||||
|
} |
||||||
|
catch (XMLStreamException ex) { |
||||||
|
throw new HttpMessageConversionException(ex.getMessage(), ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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) collectionClass.newInstance(); |
||||||
|
} |
||||||
|
catch (Exception ex) { |
||||||
|
throw new IllegalArgumentException( |
||||||
|
"Could not instantiate collection class [" + |
||||||
|
collectionClass.getName() + "]: " + ex.getMessage()); |
||||||
|
} |
||||||
|
} |
||||||
|
else if (List.class.equals(collectionClass)) { |
||||||
|
return (T) new ArrayList(); |
||||||
|
} |
||||||
|
else if (SortedSet.class.equals(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 |
||||||
|
protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException { |
||||||
|
throw new UnsupportedOperationException(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a {@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. |
||||||
|
* |
||||||
|
* @return the created factory |
||||||
|
*/ |
||||||
|
protected XMLInputFactory createXmlInputFactory() { |
||||||
|
return XMLInputFactory.newInstance(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,189 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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 static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
|
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAttribute; |
||||||
|
import javax.xml.bind.annotation.XmlElement; |
||||||
|
import javax.xml.bind.annotation.XmlRootElement; |
||||||
|
import javax.xml.bind.annotation.XmlType; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.core.ParameterizedTypeReference; |
||||||
|
import org.springframework.http.MockHttpInputMessage; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test fixture for {@link Jaxb2CollectionHttpMessageConverter}. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public class Jaxb2CollectionHttpMessageConverterTests { |
||||||
|
|
||||||
|
private Jaxb2CollectionHttpMessageConverter<?> converter; |
||||||
|
|
||||||
|
private Type rootElementListType; |
||||||
|
|
||||||
|
private Type rootElementSetType; |
||||||
|
|
||||||
|
private Type typeListType; |
||||||
|
|
||||||
|
private Type typeSetType; |
||||||
|
|
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
converter = new Jaxb2CollectionHttpMessageConverter<Collection<Object>>(); |
||||||
|
rootElementListType = new ParameterizedTypeReference<List<RootElement>>() {}.getType(); |
||||||
|
rootElementSetType = new ParameterizedTypeReference<Set<RootElement>>() {}.getType(); |
||||||
|
typeListType = new ParameterizedTypeReference<List<TestType>>() {}.getType(); |
||||||
|
typeSetType = new ParameterizedTypeReference<Set<TestType>>() {}.getType(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void canRead() throws Exception { |
||||||
|
assertTrue(converter.canRead(rootElementListType, null)); |
||||||
|
assertTrue(converter.canRead(rootElementSetType, null)); |
||||||
|
assertTrue(converter.canRead(typeSetType, null)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void readXmlRootElementList() throws Exception { |
||||||
|
String content = "<list><rootElement><type s=\"1\"/></rootElement><rootElement><type s=\"2\"/></rootElement></list>"; |
||||||
|
MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); |
||||||
|
|
||||||
|
List<RootElement> result = (List<RootElement>) converter.read(rootElementListType, inputMessage); |
||||||
|
|
||||||
|
assertEquals("Invalid result", 2, result.size()); |
||||||
|
assertEquals("Invalid result", "1", result.get(0).type.s); |
||||||
|
assertEquals("Invalid result", "2", result.get(1).type.s); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void readXmlRootElementSet() throws Exception { |
||||||
|
String content = "<set><rootElement><type s=\"1\"/></rootElement><rootElement><type s=\"2\"/></rootElement></set>"; |
||||||
|
MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); |
||||||
|
|
||||||
|
Set<RootElement> result = (Set<RootElement>) converter.read(rootElementSetType, inputMessage); |
||||||
|
|
||||||
|
assertEquals("Invalid result", 2, result.size()); |
||||||
|
assertTrue("Invalid result", result.contains(new RootElement("1"))); |
||||||
|
assertTrue("Invalid result", result.contains(new RootElement("2"))); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void readXmlTypeList() throws Exception { |
||||||
|
String content = "<list><foo s=\"1\"/><bar s=\"2\"/></list>"; |
||||||
|
MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); |
||||||
|
|
||||||
|
List<TestType> result = (List<TestType>) converter.read(typeListType, inputMessage); |
||||||
|
|
||||||
|
assertEquals("Invalid result", 2, result.size()); |
||||||
|
assertEquals("Invalid result", "1", result.get(0).s); |
||||||
|
assertEquals("Invalid result", "2", result.get(1).s); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void readXmlTypeSet() throws Exception { |
||||||
|
String content = "<set><foo s=\"1\"/><bar s=\"2\"/></set>"; |
||||||
|
MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); |
||||||
|
|
||||||
|
Set<TestType> result = (Set<TestType>) converter.read(typeSetType, inputMessage); |
||||||
|
|
||||||
|
assertEquals("Invalid result", 2, result.size()); |
||||||
|
assertTrue("Invalid result", result.contains(new TestType("1"))); |
||||||
|
assertTrue("Invalid result", result.contains(new TestType("2"))); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@XmlRootElement |
||||||
|
public static class RootElement { |
||||||
|
|
||||||
|
public RootElement() { |
||||||
|
} |
||||||
|
|
||||||
|
public RootElement(String s) { |
||||||
|
this.type = new TestType(s); |
||||||
|
} |
||||||
|
|
||||||
|
@XmlElement |
||||||
|
public TestType type = new TestType(); |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object o) { |
||||||
|
if (this == o) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (o instanceof RootElement) { |
||||||
|
RootElement other = (RootElement) o; |
||||||
|
return this.type.equals(other.type); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return type.hashCode(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@XmlType |
||||||
|
public static class TestType { |
||||||
|
|
||||||
|
public TestType() { |
||||||
|
} |
||||||
|
|
||||||
|
public TestType(String s) { |
||||||
|
this.s = s; |
||||||
|
} |
||||||
|
|
||||||
|
@XmlAttribute |
||||||
|
public String s = "Hello World"; |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object o) { |
||||||
|
if (this == o) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (o instanceof TestType) { |
||||||
|
TestType other = (TestType) o; |
||||||
|
return this.s.equals(other.s); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return s.hashCode(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,196 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2012 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.web.client; |
||||||
|
|
||||||
|
import static org.easymock.EasyMock.createMock; |
||||||
|
import static org.easymock.EasyMock.expect; |
||||||
|
import static org.easymock.EasyMock.replay; |
||||||
|
import static org.easymock.EasyMock.verify; |
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertNull; |
||||||
|
import static org.junit.Assert.fail; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.core.ParameterizedTypeReference; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpStatus; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.client.ClientHttpResponse; |
||||||
|
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test fixture for {@link HttpMessageConverter}. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public class HttpMessageConverterExtractorTests { |
||||||
|
|
||||||
|
private HttpMessageConverterExtractor extractor; |
||||||
|
|
||||||
|
private ClientHttpResponse response; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void createMocks() { |
||||||
|
response = createMock(ClientHttpResponse.class); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void noContent() throws IOException { |
||||||
|
HttpMessageConverter<?> converter = createMock(HttpMessageConverter.class); |
||||||
|
|
||||||
|
extractor = new HttpMessageConverterExtractor<String>(String.class, createConverterList(converter)); |
||||||
|
|
||||||
|
expect(response.getStatusCode()).andReturn(HttpStatus.NO_CONTENT); |
||||||
|
|
||||||
|
replay(response, converter); |
||||||
|
Object result = extractor.extractData(response); |
||||||
|
|
||||||
|
assertNull(result); |
||||||
|
verify(response, converter); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void notModified() throws IOException { |
||||||
|
HttpMessageConverter<?> converter = createMock(HttpMessageConverter.class); |
||||||
|
|
||||||
|
extractor = new HttpMessageConverterExtractor<String>(String.class, createConverterList(converter)); |
||||||
|
|
||||||
|
expect(response.getStatusCode()).andReturn(HttpStatus.NOT_MODIFIED); |
||||||
|
|
||||||
|
replay(response, converter); |
||||||
|
Object result = extractor.extractData(response); |
||||||
|
|
||||||
|
assertNull(result); |
||||||
|
verify(response, converter); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void zeroContentLength() throws IOException { |
||||||
|
HttpMessageConverter<?> converter = createMock(HttpMessageConverter.class); |
||||||
|
HttpHeaders responseHeaders = new HttpHeaders(); |
||||||
|
responseHeaders.setContentLength(0); |
||||||
|
|
||||||
|
extractor = new HttpMessageConverterExtractor<String>(String.class, createConverterList(converter)); |
||||||
|
|
||||||
|
expect(response.getStatusCode()).andReturn(HttpStatus.OK); |
||||||
|
expect(response.getHeaders()).andReturn(responseHeaders); |
||||||
|
|
||||||
|
replay(response, converter); |
||||||
|
Object result = extractor.extractData(response); |
||||||
|
|
||||||
|
assertNull(result); |
||||||
|
verify(response, converter); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void normal() throws IOException { |
||||||
|
HttpMessageConverter<String> converter = createMock(HttpMessageConverter.class); |
||||||
|
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>(); |
||||||
|
converters.add(converter); |
||||||
|
|
||||||
|
HttpHeaders responseHeaders = new HttpHeaders(); |
||||||
|
MediaType contentType = MediaType.TEXT_PLAIN; |
||||||
|
responseHeaders.setContentType(contentType); |
||||||
|
String expected = "Foo"; |
||||||
|
|
||||||
|
extractor = new HttpMessageConverterExtractor<String>(String.class, converters); |
||||||
|
|
||||||
|
expect(response.getStatusCode()).andReturn(HttpStatus.OK); |
||||||
|
expect(response.getHeaders()).andReturn(responseHeaders).times(2); |
||||||
|
expect(converter.canRead(String.class, contentType)).andReturn(true); |
||||||
|
expect(converter.read(String.class, response)).andReturn(expected); |
||||||
|
|
||||||
|
replay(response, converter); |
||||||
|
Object result = extractor.extractData(response); |
||||||
|
|
||||||
|
assertEquals(expected, result); |
||||||
|
verify(response, converter); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void cannotRead() throws IOException { |
||||||
|
HttpMessageConverter<String> converter = createMock(HttpMessageConverter.class); |
||||||
|
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>(); |
||||||
|
converters.add(converter); |
||||||
|
|
||||||
|
HttpHeaders responseHeaders = new HttpHeaders(); |
||||||
|
MediaType contentType = MediaType.TEXT_PLAIN; |
||||||
|
responseHeaders.setContentType(contentType); |
||||||
|
|
||||||
|
extractor = new HttpMessageConverterExtractor<String>(String.class, converters); |
||||||
|
|
||||||
|
expect(response.getStatusCode()).andReturn(HttpStatus.OK); |
||||||
|
expect(response.getHeaders()).andReturn(responseHeaders).times(2); |
||||||
|
expect(converter.canRead(String.class, contentType)).andReturn(false); |
||||||
|
|
||||||
|
replay(response, converter); |
||||||
|
try { |
||||||
|
extractor.extractData(response); |
||||||
|
fail("RestClientException expected"); |
||||||
|
} |
||||||
|
catch (RestClientException expected) { |
||||||
|
// expected
|
||||||
|
} |
||||||
|
|
||||||
|
verify(response, converter); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
public void generics() throws IOException { |
||||||
|
GenericHttpMessageConverter<String> converter = createMock(GenericHttpMessageConverter.class); |
||||||
|
List<HttpMessageConverter<?>> converters = createConverterList(converter); |
||||||
|
|
||||||
|
HttpHeaders responseHeaders = new HttpHeaders(); |
||||||
|
MediaType contentType = MediaType.TEXT_PLAIN; |
||||||
|
responseHeaders.setContentType(contentType); |
||||||
|
String expected = "Foo"; |
||||||
|
|
||||||
|
ParameterizedTypeReference<List<String>> reference = new ParameterizedTypeReference<List<String>>() {}; |
||||||
|
Type type = reference.getType(); |
||||||
|
|
||||||
|
extractor = new HttpMessageConverterExtractor<List<String>>(type, converters); |
||||||
|
|
||||||
|
expect(response.getStatusCode()).andReturn(HttpStatus.OK); |
||||||
|
expect(response.getHeaders()).andReturn(responseHeaders).times(2); |
||||||
|
expect(converter.canRead(type, contentType)).andReturn(true); |
||||||
|
expect(converter.read(type, response)).andReturn(expected); |
||||||
|
|
||||||
|
replay(response, converter); |
||||||
|
Object result = extractor.extractData(response); |
||||||
|
|
||||||
|
assertEquals(expected, result); |
||||||
|
verify(response, converter); |
||||||
|
} |
||||||
|
|
||||||
|
private List<HttpMessageConverter<?>> createConverterList(HttpMessageConverter converter) { |
||||||
|
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>(1); |
||||||
|
converters.add(converter); |
||||||
|
return converters; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue