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