Support generic target types in the RestTemplate
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-7023
This commit is contained in:
parent
789e12a0c7
commit
ed3823b045
|
@ -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;
|
||||
|
||||
}
|
|
@ -17,17 +17,10 @@
|
|||
package org.springframework.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonEncoding;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
@ -36,6 +29,15 @@ import com.fasterxml.jackson.databind.JavaType;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
|
||||
* that can read and write JSON using <a href="http://jackson.codehaus.org/">Jackson 2's</a> {@link ObjectMapper}.
|
||||
|
@ -50,7 +52,8 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
|||
* @since 3.1.2
|
||||
* @see org.springframework.web.servlet.view.json.MappingJackson2JsonView
|
||||
*/
|
||||
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object> {
|
||||
public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object>
|
||||
implements GenericHttpMessageConverter<Object> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
|
@ -63,7 +66,7 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
|||
|
||||
|
||||
/**
|
||||
* Construct a new {@code BindingJacksonHttpMessageConverter}.
|
||||
* Construct a new {@code MappingJackson2HttpMessageConverter}.
|
||||
*/
|
||||
public MappingJackson2HttpMessageConverter() {
|
||||
super(new MediaType("application", "json", DEFAULT_CHARSET));
|
||||
|
@ -125,7 +128,11 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
|||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
JavaType javaType = getJavaType(clazz);
|
||||
return canRead((Type) clazz, mediaType);
|
||||
}
|
||||
|
||||
public boolean canRead(Type type, MediaType mediaType) {
|
||||
JavaType javaType = getJavaType(type);
|
||||
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
|
||||
}
|
||||
|
||||
|
@ -145,6 +152,17 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
|||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
JavaType javaType = getJavaType(clazz);
|
||||
return readJavaType(javaType, inputMessage);
|
||||
}
|
||||
|
||||
public Object read(Type type, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
JavaType javaType = getJavaType(type);
|
||||
return readJavaType(javaType, inputMessage);
|
||||
}
|
||||
|
||||
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
|
||||
try {
|
||||
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
|
||||
}
|
||||
|
@ -153,6 +171,7 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
|
@ -180,24 +199,24 @@ public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConv
|
|||
|
||||
|
||||
/**
|
||||
* Return the Jackson {@link JavaType} for the specified class.
|
||||
* Return the Jackson {@link JavaType} for the specified type.
|
||||
* <p>The default implementation returns {@link ObjectMapper#constructType(java.lang.reflect.Type)},
|
||||
* but this can be overridden in subclasses, to allow for custom generic collection handling.
|
||||
* For instance:
|
||||
* <pre class="code">
|
||||
* protected JavaType getJavaType(Class<?> clazz) {
|
||||
* if (List.class.isAssignableFrom(clazz)) {
|
||||
* return objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, MyBean.class);
|
||||
* protected JavaType getJavaType(Type type) {
|
||||
* if (type instanceof Class && List.class.isAssignableFrom((Class)type)) {
|
||||
* return TypeFactory.collectionType(ArrayList.class, MyBean.class);
|
||||
* } else {
|
||||
* return super.getJavaType(clazz);
|
||||
* return super.getJavaType(type);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param clazz the class to return the java type for
|
||||
* @param type the type to return the java type for
|
||||
* @return the java type
|
||||
*/
|
||||
protected JavaType getJavaType(Class<?> clazz) {
|
||||
return objectMapper.constructType(clazz);
|
||||
protected JavaType getJavaType(Type type) {
|
||||
return this.objectMapper.constructType(type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
|
@ -17,9 +17,11 @@
|
|||
package org.springframework.http.converter.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.JsonProcessingException;
|
||||
|
@ -27,16 +29,16 @@ import org.codehaus.jackson.map.ObjectMapper;
|
|||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.codehaus.jackson.map.type.TypeFactory;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter}
|
||||
* that can read and write JSON using <a href="http://jackson.codehaus.org/">Jackson's</a> {@link ObjectMapper}.
|
||||
|
@ -50,7 +52,8 @@ import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
|||
* @since 3.0
|
||||
* @see org.springframework.web.servlet.view.json.MappingJacksonJsonView
|
||||
*/
|
||||
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
|
||||
public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConverter<Object>
|
||||
implements GenericHttpMessageConverter<Object> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
|
||||
|
@ -63,7 +66,7 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
|
|||
|
||||
|
||||
/**
|
||||
* Construct a new {@code BindingJacksonHttpMessageConverter}.
|
||||
* Construct a new {@code MappingJacksonHttpMessageConverter}.
|
||||
*/
|
||||
public MappingJacksonHttpMessageConverter() {
|
||||
super(new MediaType("application", "json", DEFAULT_CHARSET));
|
||||
|
@ -125,7 +128,11 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
|
|||
|
||||
@Override
|
||||
public boolean canRead(Class<?> clazz, MediaType mediaType) {
|
||||
JavaType javaType = getJavaType(clazz);
|
||||
return canRead((Type) clazz, mediaType);
|
||||
}
|
||||
|
||||
public boolean canRead(Type type, MediaType mediaType) {
|
||||
JavaType javaType = getJavaType(type);
|
||||
return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
|
||||
}
|
||||
|
||||
|
@ -145,6 +152,17 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
|
|||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
JavaType javaType = getJavaType(clazz);
|
||||
return readJavaType(javaType, inputMessage);
|
||||
}
|
||||
|
||||
public Object read(Type type, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
|
||||
JavaType javaType = getJavaType(type);
|
||||
return readJavaType(javaType, inputMessage);
|
||||
}
|
||||
|
||||
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
|
||||
try {
|
||||
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
|
||||
}
|
||||
|
@ -180,24 +198,24 @@ public class MappingJacksonHttpMessageConverter extends AbstractHttpMessageConve
|
|||
|
||||
|
||||
/**
|
||||
* Return the Jackson {@link JavaType} for the specified class.
|
||||
* Return the Jackson {@link JavaType} for the specified type.
|
||||
* <p>The default implementation returns {@link TypeFactory#type(java.lang.reflect.Type)},
|
||||
* but this can be overridden in subclasses, to allow for custom generic collection handling.
|
||||
* For instance:
|
||||
* <pre class="code">
|
||||
* protected JavaType getJavaType(Class<?> clazz) {
|
||||
* if (List.class.isAssignableFrom(clazz)) {
|
||||
* protected JavaType getJavaType(Type type) {
|
||||
* if (type instanceof Class && List.class.isAssignableFrom((Class)type)) {
|
||||
* return TypeFactory.collectionType(ArrayList.class, MyBean.class);
|
||||
* } else {
|
||||
* return super.getJavaType(clazz);
|
||||
* return super.getJavaType(type);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* @param clazz the class to return the java type for
|
||||
* @param type the type to return the java type for
|
||||
* @return the java type
|
||||
*/
|
||||
protected JavaType getJavaType(Class<?> clazz) {
|
||||
return TypeFactory.type(clazz);
|
||||
protected JavaType getJavaType(Type type) {
|
||||
return TypeFactory.type(type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.web.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
@ -25,12 +26,13 @@ import org.apache.commons.logging.LogFactory;
|
|||
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;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Response extractor that uses the given {@linkplain HttpMessageConverter entity converters} to convert the response
|
||||
* into a type <code>T</code>.
|
||||
* Response extractor that uses the given {@linkplain HttpMessageConverter entity
|
||||
* converters} to convert the response into a type <code>T</code>.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see RestTemplate
|
||||
|
@ -38,21 +40,31 @@ import org.springframework.util.Assert;
|
|||
*/
|
||||
public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
|
||||
|
||||
private final Class<T> responseType;
|
||||
private final Type responseType;
|
||||
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
private final Log logger;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the {@code HttpMessageConverterExtractor} with the given response type and message
|
||||
* converters. The given converters must support the response type.
|
||||
* Creates a new instance of the {@code HttpMessageConverterExtractor} with the given
|
||||
* response type and message converters. The given converters must support the response
|
||||
* type.
|
||||
*/
|
||||
public HttpMessageConverterExtractor(Class<T> responseType, List<HttpMessageConverter<?>> messageConverters) {
|
||||
this((Type) responseType, messageConverters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the {@code HttpMessageConverterExtractor} with the given
|
||||
* response type and message converters. The given converters must support the response
|
||||
* type.
|
||||
*/
|
||||
public HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters) {
|
||||
this(responseType, messageConverters, LogFactory.getLog(HttpMessageConverterExtractor.class));
|
||||
}
|
||||
|
||||
HttpMessageConverterExtractor(Class<T> responseType, List<HttpMessageConverter<?>> messageConverters, Log logger) {
|
||||
HttpMessageConverterExtractor(Type responseType, List<HttpMessageConverter<?>> messageConverters, Log logger) {
|
||||
Assert.notNull(responseType, "'responseType' must not be null");
|
||||
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
|
||||
this.responseType = responseType;
|
||||
|
@ -65,6 +77,39 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
|
|||
if (!hasMessageBody(response)) {
|
||||
return null;
|
||||
}
|
||||
MediaType contentType = getContentType(response);
|
||||
|
||||
Class<T> responseClass = null;
|
||||
if (this.responseType instanceof Class) {
|
||||
responseClass = (Class) this.responseType;
|
||||
}
|
||||
for (HttpMessageConverter messageConverter : this.messageConverters) {
|
||||
if (responseClass != null) {
|
||||
if (messageConverter.canRead(responseClass, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + responseClass.getName() + "] as \"" +
|
||||
contentType + "\" using [" + messageConverter + "]");
|
||||
}
|
||||
return (T) messageConverter.read(responseClass, response);
|
||||
}
|
||||
}
|
||||
else if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
GenericHttpMessageConverter genericMessageConverter = (GenericHttpMessageConverter) messageConverter;
|
||||
if (genericMessageConverter.canRead(this.responseType, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + this.responseType + "] as \"" +
|
||||
contentType + "\" using [" + messageConverter + "]");
|
||||
}
|
||||
return (T) genericMessageConverter.read(this.responseType, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RestClientException(
|
||||
"Could not extract response: no suitable HttpMessageConverter found for response type [" +
|
||||
this.responseType + "] and content type [" + contentType + "]");
|
||||
}
|
||||
|
||||
private MediaType getContentType(ClientHttpResponse response) {
|
||||
MediaType contentType = response.getHeaders().getContentType();
|
||||
if (contentType == null) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
|
@ -72,24 +117,13 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
|
|||
}
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
for (HttpMessageConverter messageConverter : messageConverters) {
|
||||
if (messageConverter.canRead(responseType, contentType)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Reading [" + responseType.getName() + "] as \"" + contentType
|
||||
+"\" using [" + messageConverter + "]");
|
||||
}
|
||||
return (T) messageConverter.read(this.responseType, response);
|
||||
}
|
||||
}
|
||||
throw new RestClientException(
|
||||
"Could not extract response: no suitable HttpMessageConverter found for response type [" +
|
||||
this.responseType.getName() + "] and content type [" + contentType + "]");
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given response has a message body.
|
||||
* <p>Default implementation returns {@code false} for a response status of {@code 204} or {@code 304}, or a
|
||||
* {@code Content-Length} of {@code 0}.
|
||||
* Indicates whether the given response has a message body. <p>Default implementation
|
||||
* returns {@code false} for a response status of {@code 204} or {@code 304}, or a {@code
|
||||
* Content-Length} of {@code 0}.
|
||||
*
|
||||
* @param response the response to check for a message body
|
||||
* @return {@code true} if the response has a body, {@code false} otherwise
|
||||
|
@ -97,7 +131,8 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
|
|||
*/
|
||||
protected boolean hasMessageBody(ClientHttpResponse response) throws IOException {
|
||||
HttpStatus responseStatus = response.getStatusCode();
|
||||
if (responseStatus == HttpStatus.NO_CONTENT || responseStatus == HttpStatus.NOT_MODIFIED) {
|
||||
if (responseStatus == HttpStatus.NO_CONTENT ||
|
||||
responseStatus == HttpStatus.NOT_MODIFIED) {
|
||||
return false;
|
||||
}
|
||||
long contentLength = response.getHeaders().getContentLength();
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
|
@ -20,6 +20,7 @@ import java.net.URI;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
@ -396,6 +397,69 @@ public interface RestOperations {
|
|||
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
|
||||
Class<T> responseType) throws RestClientException;
|
||||
|
||||
/**
|
||||
* Execute the HTTP method to the given URI template, writing the given
|
||||
* request entity to the request, and returns the response as {@link ResponseEntity}.
|
||||
* The given {@link ParameterizedTypeReference} is used to pass generic type information:
|
||||
*
|
||||
* <pre class="code">
|
||||
* ParameterizedTypeReference<List<MyBean>> myBean = new ParameterizedTypeReference<List<MyBean>>() {};
|
||||
* ResponseEntity<List<MyBean>> response = template.exchange("http://example.com",HttpMethod.GET, null, myBean);
|
||||
* </pre>
|
||||
*
|
||||
* @param url the URL
|
||||
* @param method the HTTP method (GET, POST, etc)
|
||||
* @param requestEntity the entity (headers and/or body) to write to the
|
||||
* request, may be {@code null}
|
||||
* @param responseType the type of the return value
|
||||
* @param uriVariables the variables to expand in the template
|
||||
* @return the response as entity
|
||||
* @since 3.2.0
|
||||
*/
|
||||
<T> ResponseEntity<T> exchange(String url,HttpMethod method, HttpEntity<?> requestEntity,
|
||||
ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException;
|
||||
|
||||
/**
|
||||
* Execute the HTTP method to the given URI template, writing the given
|
||||
* request entity to the request, and returns the response as {@link ResponseEntity}.
|
||||
* The given {@link ParameterizedTypeReference} is used to pass generic type information:
|
||||
*
|
||||
* <pre class="code">
|
||||
* ParameterizedTypeReference<List<MyBean>> myBean = new ParameterizedTypeReference<List<MyBean>>() {};
|
||||
* ResponseEntity<List<MyBean>> response = template.exchange("http://example.com",HttpMethod.GET, null, myBean);
|
||||
* </pre>
|
||||
*
|
||||
* @param url the URL
|
||||
* @param method the HTTP method (GET, POST, etc)
|
||||
* @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null}
|
||||
* @param responseType the type of the return value
|
||||
* @param uriVariables the variables to expand in the template
|
||||
* @return the response as entity
|
||||
* @since 3.2.0
|
||||
*/
|
||||
<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
|
||||
ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException;
|
||||
|
||||
/**
|
||||
* Execute the HTTP method to the given URI template, writing the given
|
||||
* request entity to the request, and returns the response as {@link ResponseEntity}.
|
||||
* The given {@link ParameterizedTypeReference} is used to pass generic type information:
|
||||
*
|
||||
* <pre class="code">
|
||||
* ParameterizedTypeReference<List<MyBean>> myBean = new ParameterizedTypeReference<List<MyBean>>() {};
|
||||
* ResponseEntity<List<MyBean>> response = template.exchange("http://example.com",HttpMethod.GET, null, myBean);
|
||||
* </pre>
|
||||
*
|
||||
* @param url the URL
|
||||
* @param method the HTTP method (GET, POST, etc)
|
||||
* @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null}
|
||||
* @param responseType the type of the return value
|
||||
* @return the response as entity
|
||||
* @since 3.2.0
|
||||
*/
|
||||
<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
|
||||
ParameterizedTypeReference<T> responseType) throws RestClientException;
|
||||
|
||||
// general execution
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 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
|
||||
* 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,
|
||||
|
@ -18,6 +18,7 @@ package org.springframework.web.client;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -25,6 +26,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
@ -35,6 +37,7 @@ import org.springframework.http.client.ClientHttpRequestFactory;
|
|||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.client.support.InterceptingHttpAccessor;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.ResourceHttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
|
@ -384,6 +387,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
|
||||
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
|
||||
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
|
||||
|
||||
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
|
||||
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
|
||||
return execute(url, method, requestCallback, responseExtractor, uriVariables);
|
||||
|
@ -391,6 +395,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
|
||||
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
|
||||
HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
|
||||
|
||||
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
|
||||
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
|
||||
return execute(url, method, requestCallback, responseExtractor, uriVariables);
|
||||
|
@ -398,11 +403,39 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
|
||||
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
|
||||
Class<T> responseType) throws RestClientException {
|
||||
|
||||
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
|
||||
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
|
||||
return execute(url, method, requestCallback, responseExtractor);
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
|
||||
ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
|
||||
|
||||
Type type = responseType.getType();
|
||||
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, type);
|
||||
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(type);
|
||||
return execute(url, method, requestCallback, responseExtractor, uriVariables);
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity,
|
||||
ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
|
||||
|
||||
Type type = responseType.getType();
|
||||
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, type);
|
||||
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(type);
|
||||
return execute(url, method, requestCallback, responseExtractor, uriVariables);
|
||||
}
|
||||
|
||||
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
|
||||
ParameterizedTypeReference<T> responseType) throws RestClientException {
|
||||
|
||||
Type type = responseType.getType();
|
||||
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, type);
|
||||
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(type);
|
||||
return execute(url, method, requestCallback, responseExtractor);
|
||||
}
|
||||
|
||||
// general execution
|
||||
|
||||
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
|
||||
|
@ -504,37 +537,62 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
*/
|
||||
private class AcceptHeaderRequestCallback implements RequestCallback {
|
||||
|
||||
private final Class<?> responseType;
|
||||
private final Type responseType;
|
||||
|
||||
private AcceptHeaderRequestCallback(Class<?> responseType) {
|
||||
private AcceptHeaderRequestCallback(Type responseType) {
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void doWithRequest(ClientHttpRequest request) throws IOException {
|
||||
if (responseType != null) {
|
||||
Class<?> responseClass = null;
|
||||
if (responseType instanceof Class) {
|
||||
responseClass = (Class) responseType;
|
||||
}
|
||||
|
||||
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
|
||||
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
|
||||
if (messageConverter.canRead(responseType, null)) {
|
||||
List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
|
||||
for (MediaType supportedMediaType : supportedMediaTypes) {
|
||||
if (supportedMediaType.getCharSet() != null) {
|
||||
supportedMediaType =
|
||||
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
|
||||
}
|
||||
allSupportedMediaTypes.add(supportedMediaType);
|
||||
if (responseClass != null) {
|
||||
if (messageConverter.canRead(responseClass, null)) {
|
||||
allSupportedMediaTypes
|
||||
.addAll(getSupportedMediaTypes(messageConverter));
|
||||
}
|
||||
}
|
||||
else if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||
|
||||
GenericHttpMessageConverter genericMessageConverter =
|
||||
(GenericHttpMessageConverter) messageConverter;
|
||||
if (genericMessageConverter.canRead(responseType, null)) {
|
||||
allSupportedMediaTypes
|
||||
.addAll(getSupportedMediaTypes(messageConverter));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!allSupportedMediaTypes.isEmpty()) {
|
||||
MediaType.sortBySpecificity(allSupportedMediaTypes);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
|
||||
logger.debug("Setting request Accept header to " +
|
||||
allSupportedMediaTypes);
|
||||
}
|
||||
request.getHeaders().setAccept(allSupportedMediaTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<MediaType> getSupportedMediaTypes(HttpMessageConverter<?> messageConverter) {
|
||||
List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
|
||||
List<MediaType> result = new ArrayList<MediaType>(supportedMediaTypes.size());
|
||||
for (MediaType supportedMediaType : supportedMediaTypes) {
|
||||
if (supportedMediaType.getCharSet() != null) {
|
||||
supportedMediaType =
|
||||
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
|
||||
}
|
||||
result.add(supportedMediaType);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -550,7 +608,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private HttpEntityRequestCallback(Object requestBody, Class<?> responseType) {
|
||||
private HttpEntityRequestCallback(Object requestBody, Type responseType) {
|
||||
super(responseType);
|
||||
if (requestBody instanceof HttpEntity) {
|
||||
this.requestEntity = (HttpEntity) requestBody;
|
||||
|
@ -618,7 +676,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
|||
|
||||
private final HttpMessageConverterExtractor<T> delegate;
|
||||
|
||||
public ResponseEntityResponseExtractor(Class<T> responseType) {
|
||||
public ResponseEntityResponseExtractor(Type responseType) {
|
||||
if (responseType != null && !Void.class.equals(responseType)) {
|
||||
this.delegate = new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
|
||||
} else {
|
||||
|
|
|
@ -16,23 +16,22 @@
|
|||
|
||||
package org.springframework.http.converter.json;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MockHttpInputMessage;
|
||||
import org.springframework.http.MockHttpOutputMessage;
|
||||
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Jackson 2.x converter tests.
|
||||
*
|
||||
|
@ -52,12 +51,12 @@ public class MappingJackson2HttpMessageConverterTests extends AbstractMappingJac
|
|||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter() {
|
||||
|
||||
@Override
|
||||
protected JavaType getJavaType(Class<?> clazz) {
|
||||
if (List.class.isAssignableFrom(clazz)) {
|
||||
protected JavaType getJavaType(Type type) {
|
||||
if (type instanceof Class && List.class.isAssignableFrom((Class)type)) {
|
||||
return new ObjectMapper().getTypeFactory().constructCollectionType(ArrayList.class, MyBean.class);
|
||||
}
|
||||
else {
|
||||
return super.getJavaType(clazz);
|
||||
return super.getJavaType(type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -77,6 +76,29 @@ public class MappingJackson2HttpMessageConverterTests extends AbstractMappingJac
|
|||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readParameterizedType() throws IOException {
|
||||
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {};
|
||||
|
||||
String body =
|
||||
"[{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
|
||||
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), inputMessage);
|
||||
assertEquals(1, results.size());
|
||||
MyBean result = results.get(0);
|
||||
assertEquals("Foo", result.getString());
|
||||
assertEquals(42, result.getNumber());
|
||||
assertEquals(42F, result.getFraction(), 0F);
|
||||
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
|
||||
assertTrue(result.isBool());
|
||||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void prettyPrint() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
|
|
@ -16,18 +16,18 @@
|
|||
|
||||
package org.springframework.http.converter.json;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.map.type.TypeFactory;
|
||||
import org.codehaus.jackson.type.JavaType;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.MockHttpInputMessage;
|
||||
import org.springframework.http.MockHttpOutputMessage;
|
||||
|
@ -49,12 +49,12 @@ public class MappingJacksonHttpMessageConverterTests extends AbstractMappingJack
|
|||
public void readGenerics() throws IOException {
|
||||
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter() {
|
||||
@Override
|
||||
protected JavaType getJavaType(Class<?> clazz) {
|
||||
if (List.class.isAssignableFrom(clazz)) {
|
||||
protected JavaType getJavaType(Type type) {
|
||||
if (type instanceof Class && List.class.isAssignableFrom((Class)type)) {
|
||||
return TypeFactory.collectionType(ArrayList.class, MyBean.class);
|
||||
}
|
||||
else {
|
||||
return super.getJavaType(clazz);
|
||||
return super.getJavaType(type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -74,6 +74,28 @@ public class MappingJacksonHttpMessageConverterTests extends AbstractMappingJack
|
|||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readParameterizedType() throws IOException {
|
||||
ParameterizedTypeReference<List<MyBean>> beansList = new ParameterizedTypeReference<List<MyBean>>() {};
|
||||
|
||||
String body =
|
||||
"[{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}]";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
|
||||
inputMessage.getHeaders().setContentType(new MediaType("application", "json"));
|
||||
|
||||
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
|
||||
List<MyBean> results = (List<MyBean>) converter.read(beansList.getType(), inputMessage);
|
||||
assertEquals(1, results.size());
|
||||
MyBean result = results.get(0);
|
||||
assertEquals("Foo", result.getString());
|
||||
assertEquals(42, result.getNumber());
|
||||
assertEquals(42F, result.getFraction(), 0F);
|
||||
assertArrayEquals(new String[]{"Foo", "Bar"}, result.getArray());
|
||||
assertTrue(result.isBool());
|
||||
assertArrayEquals(new byte[]{0x1, 0x2}, result.getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void prettyPrint() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* 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
|
||||
* 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,
|
||||
|
@ -21,12 +21,16 @@ import java.net.URI;
|
|||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.easymock.EasyMock.*;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
@ -36,11 +40,9 @@ import org.springframework.http.ResponseEntity;
|
|||
import org.springframework.http.client.ClientHttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
import static org.easymock.EasyMock.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
@SuppressWarnings("unchecked")
|
||||
public class RestTemplateTests {
|
||||
|
@ -600,9 +602,8 @@ public class RestTemplateTests {
|
|||
|
||||
@Test
|
||||
public void exchange() throws Exception {
|
||||
MediaType textPlain = new MediaType("text", "plain");
|
||||
expect(converter.canRead(Integer.class, null)).andReturn(true);
|
||||
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain));
|
||||
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(this.request);
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
expect(this.request.getHeaders()).andReturn(requestHeaders).times(2);
|
||||
|
@ -612,12 +613,12 @@ public class RestTemplateTests {
|
|||
expect(this.request.execute()).andReturn(response);
|
||||
expect(errorHandler.hasError(response)).andReturn(false);
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.setContentType(textPlain);
|
||||
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
responseHeaders.setContentLength(10);
|
||||
expect(response.getStatusCode()).andReturn(HttpStatus.OK);
|
||||
expect(response.getHeaders()).andReturn(responseHeaders).times(3);
|
||||
Integer expected = 42;
|
||||
expect(converter.canRead(Integer.class, textPlain)).andReturn(true);
|
||||
expect(converter.canRead(Integer.class, MediaType.TEXT_PLAIN)).andReturn(true);
|
||||
expect(converter.read(Integer.class, response)).andReturn(expected);
|
||||
expect(response.getStatusCode()).andReturn(HttpStatus.OK);
|
||||
response.close();
|
||||
|
@ -629,14 +630,56 @@ public class RestTemplateTests {
|
|||
HttpEntity<String> requestEntity = new HttpEntity<String>(body, entityHeaders);
|
||||
ResponseEntity<Integer> result = template.exchange("http://example.com", HttpMethod.POST, requestEntity, Integer.class);
|
||||
assertEquals("Invalid POST result", expected, result.getBody());
|
||||
assertEquals("Invalid Content-Type", textPlain, result.getHeaders().getContentType());
|
||||
assertEquals("Invalid Accept header", textPlain.toString(), requestHeaders.getFirst("Accept"));
|
||||
assertEquals("Invalid Content-Type", MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
assertEquals("Invalid Accept header", MediaType.TEXT_PLAIN_VALUE, requestHeaders.getFirst("Accept"));
|
||||
assertEquals("Invalid custom header", "MyValue", requestHeaders.getFirst("MyHeader"));
|
||||
assertEquals("Invalid status code", HttpStatus.OK, result.getStatusCode());
|
||||
|
||||
verifyMocks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exchangeParameterizedType() throws Exception {
|
||||
GenericHttpMessageConverter converter = createMock(GenericHttpMessageConverter.class);
|
||||
template.setMessageConverters(Collections.<HttpMessageConverter<?>>singletonList(converter));
|
||||
|
||||
ParameterizedTypeReference<List<Integer>> intList = new ParameterizedTypeReference<List<Integer>>() {};
|
||||
expect(converter.canRead(intList.getType(), null)).andReturn(true);
|
||||
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(this.request);
|
||||
HttpHeaders requestHeaders = new HttpHeaders();
|
||||
expect(this.request.getHeaders()).andReturn(requestHeaders).times(2);
|
||||
expect(converter.canWrite(String.class, null)).andReturn(true);
|
||||
String requestBody = "Hello World";
|
||||
converter.write(requestBody, null, this.request);
|
||||
expect(this.request.execute()).andReturn(response);
|
||||
expect(errorHandler.hasError(response)).andReturn(false);
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
|
||||
responseHeaders.setContentLength(10);
|
||||
expect(response.getStatusCode()).andReturn(HttpStatus.OK);
|
||||
expect(response.getHeaders()).andReturn(responseHeaders).times(3);
|
||||
List<Integer> expected = Collections.singletonList(42);
|
||||
expect(converter.canRead(intList.getType(), MediaType.TEXT_PLAIN)).andReturn(true);
|
||||
expect(converter.read(intList.getType(), response)).andReturn(expected);
|
||||
expect(response.getStatusCode()).andReturn(HttpStatus.OK);
|
||||
response.close();
|
||||
|
||||
replay(requestFactory, request, response, errorHandler, converter);
|
||||
|
||||
HttpHeaders entityHeaders = new HttpHeaders();
|
||||
entityHeaders.set("MyHeader", "MyValue");
|
||||
HttpEntity<String> requestEntity = new HttpEntity<String>(requestBody, entityHeaders);
|
||||
ResponseEntity<List<Integer>> result = template.exchange("http://example.com", HttpMethod.POST, requestEntity, intList);
|
||||
assertEquals("Invalid POST result", expected, result.getBody());
|
||||
assertEquals("Invalid Content-Type", MediaType.TEXT_PLAIN, result.getHeaders().getContentType());
|
||||
assertEquals("Invalid Accept header", MediaType.TEXT_PLAIN_VALUE, requestHeaders.getFirst("Accept"));
|
||||
assertEquals("Invalid custom header", "MyValue", requestHeaders.getFirst("MyHeader"));
|
||||
assertEquals("Invalid status code", HttpStatus.OK, result.getStatusCode());
|
||||
|
||||
verify(requestFactory, request, response, errorHandler, converter);
|
||||
}
|
||||
|
||||
|
||||
private void replayMocks() {
|
||||
replay(requestFactory, request, response, errorHandler, converter);
|
||||
|
|
Loading…
Reference in New Issue