HttpMessageConverter.supports() is split into canRead/canWrite.

HttpMessageConverter.write() now allows for a specific content type.
This commit is contained in:
Arjen Poutsma
2009-11-27 13:23:15 +00:00
parent 18c63f70c4
commit dc0613f487
22 changed files with 628 additions and 518 deletions

View File

@@ -56,14 +56,22 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
protected AbstractHttpMessageConverter() {
}
/** Construct an {@code AbstractHttpMessageConverter} with one supported media type. */
/**
* Construct an {@code AbstractHttpMessageConverter} with one supported media type.
*
* @param supportedMediaType the supported media type
*/
protected AbstractHttpMessageConverter(MediaType supportedMediaType) {
this.supportedMediaTypes = Collections.singletonList(supportedMediaType);
setSupportedMediaTypes(Collections.singletonList(supportedMediaType));
}
/** Construct an {@code AbstractHttpMessageConverter} with multiple supported media type. */
/**
* Construct an {@code AbstractHttpMessageConverter} with multiple supported media type.
*
* @param supportedMediaTypes the supported media types
*/
protected AbstractHttpMessageConverter(MediaType... supportedMediaTypes) {
this.supportedMediaTypes = Arrays.asList(supportedMediaTypes);
setSupportedMediaTypes(Arrays.asList(supportedMediaTypes));
}
/** Set the list of {@link MediaType} objects supported by this converter. */
@@ -77,8 +85,59 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
}
/**
* {@inheritDoc} <p>This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}. Future
* implementations might add some default behavior, however.
* {@inheritDoc}
*
* <p>This implementation checks if the given class is {@linkplain #supports(Class) supported}, and if the {@linkplain
* #getSupportedMediaTypes() supported media types} {@linkplain MediaType#includes(MediaType) include} the given media
* type.
*/
public boolean canRead(Class<? extends T> clazz, MediaType mediaType) {
return supports(clazz) && isSupported(mediaType);
}
/**
* {@inheritDoc}
*
* <p>This implementation checks if the given class is {@linkplain #supports(Class) supported}, and if the {@linkplain
* #getSupportedMediaTypes() supported media types} {@linkplain MediaType#includes(MediaType) include} the given media
* type.
*/
public boolean canWrite(Class<? extends T> clazz, MediaType mediaType) {
return supports(clazz) && isSupported(mediaType);
}
/**
* Returns true if any of the {@linkplain #setSupportedMediaTypes(List) supported media types} include the given media
* type.
*
* @param mediaType the media type
* @return true if the supported media types include the media type, or if the media type is {@code null}
*/
protected boolean isSupported(MediaType mediaType) {
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
/**
* Indicates whether the given class is supported by this converter.
*
* @param clazz the class to test for support
* @return <code>true</code> if supported; <code>false</code> otherwise
*/
protected abstract boolean supports(Class<? extends T> clazz);
/**
* {@inheritDoc}
*
* <p>This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}. Future implementations
* might add some default behavior, however.
*/
public final T read(Class<T> clazz, HttpInputMessage inputMessage) throws IOException {
return readInternal(clazz, inputMessage);
@@ -97,17 +156,22 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
throws IOException, HttpMessageNotReadableException;
/**
* {@inheritDoc} <p>This implementation delegates to {@link #getContentType(Object)} and {@link
* #getContentLength(Object)}, and sets the corresponding headers on the output message. It then calls {@link
* #writeInternal(Object, HttpOutputMessage)}.
* {@inheritDoc}
*
* <p>This implementation delegates to {@link #getDefaultContentType(Object)} if a content type was not provided, calls
* {@link #getContentLength}, and sets the corresponding headers on the output message. It then calls {@link
* #writeInternal}.
*/
public final void write(T t, HttpOutputMessage outputMessage) throws IOException {
public final void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
HttpHeaders headers = outputMessage.getHeaders();
MediaType contentType = getContentType(t);
if (contentType == null) {
contentType = getDefaultContentType(t);
}
if (contentType != null) {
headers.setContentType(contentType);
}
Long contentLength = getContentLength(t);
Long contentLength = getContentLength(t, contentType);
if (contentLength != null) {
headers.setContentLength(contentLength);
}
@@ -116,30 +180,35 @@ public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConv
}
/**
* Returns the content type for the given type. <p>By default, this returns the first element of the {@link
* #setSupportedMediaTypes(List) supportedMediaTypes} property, if any. Can be overriden in subclasses.
* Returns the default content type for the given type. Called when {@link #write} is invoked without a specified
* content type parameter.
*
* <p>By default, this returns the first element of the {@link #setSupportedMediaTypes(List) supportedMediaTypes}
* property, if any. Can be overriden in subclasses.
*
* @param t the type to return the content type for
* @return the content type, or <code>null</code> if not known
*/
protected MediaType getContentType(T t) {
protected MediaType getDefaultContentType(T t) {
List<MediaType> mediaTypes = getSupportedMediaTypes();
return (!mediaTypes.isEmpty() ? mediaTypes.get(0) : null);
}
/**
* Returns the content length for the given type. <p>By default, this returns <code>null</code>. Can be overriden in
* Returns the content length for the given type.
*
* <p>By default, this returns {@code null}, meaning that the content length is unknown. Can be overriden in
* subclasses.
*
* @param t the type to return the content length for
* @return the content length, or <code>null</code> if not known
* @return the content length, or {@code null} if not known
*/
protected Long getContentLength(T t) {
protected Long getContentLength(T t, MediaType contentType) {
return null;
}
/**
* Abstract template method that writes the actual body. Invoked from {@link #write(Object, HttpOutputMessage)}.
* Abstract template method that writes the actual body. Invoked from {@link #write}.
*
* @param t the object to write to the output message
* @param outputMessage the message to write to

View File

@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.imageio.IIOImage;
@@ -48,8 +49,7 @@ import org.springframework.util.Assert;
* <p>By default, this converter can read all media types that are supported by the {@linkplain
* ImageIO#getReaderMIMETypes() registered image readers}, and writes using the media type of the first available
* {@linkplain javax.imageio.ImageIO#getWriterMIMETypes() registered image writer}. This behavior can be overriden by
* setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} and {@link
* #setContentType(org.springframework.http.MediaType) contentType} properties respectively.
* setting the #setContentType(org.springframework.http.MediaType) contentType} properties.
*
* <p>If the {@link #setCacheDir(java.io.File) cacheDir} property is set to an existing directory, this converter will
* cache image data.
@@ -60,57 +60,50 @@ import org.springframework.util.Assert;
* @author Arjen Poutsma
* @since 3.0
*/
public class BufferedImageHttpMessageConverter extends AbstractHttpMessageConverter<BufferedImage> {
public class BufferedImageHttpMessageConverter implements HttpMessageConverter<BufferedImage> {
private MediaType contentType;
private List<MediaType> readableMediaTypes = new ArrayList<MediaType>();
private MediaType defaultContentType;
private File cacheDir;
public BufferedImageHttpMessageConverter() {
String[] readerMediaTypes = ImageIO.getReaderMIMETypes();
List<MediaType> supportedMediaTypes = new ArrayList<MediaType>(readerMediaTypes.length);
for (String mediaType : readerMediaTypes) {
supportedMediaTypes.add(MediaType.parseMediaType(mediaType));
readableMediaTypes.add(MediaType.parseMediaType(mediaType));
}
setSupportedMediaTypes(supportedMediaTypes);
String[] writerMediaTypes = ImageIO.getWriterMIMETypes();
if (writerMediaTypes.length > 0) {
contentType = MediaType.parseMediaType(writerMediaTypes[0]);
defaultContentType = MediaType.parseMediaType(writerMediaTypes[0]);
}
}
/**
* Sets the {@link MediaType MediaTypes} supported for reading.
* Returns the default {@code Content-Type} to be used for writing. Called when {@link #write} is invoked without a
* specified content type parameter.
*
* @throws IllegalArgumentException if the given media type is not supported by the Java Image I/O API
* @return the default content type
*/
@Override
public void setSupportedMediaTypes(List<MediaType> supportedMediaTypes) {
Assert.notEmpty(supportedMediaTypes, "'supportedMediaTypes' must not be empty");
for (MediaType supportedMediaType : supportedMediaTypes) {
Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(supportedMediaType.toString());
if (!imageReaders.hasNext()) {
throw new IllegalArgumentException(
"MediaType [" + supportedMediaType + "] is not supported by the Java Image I/O API");
}
}
super.setSupportedMediaTypes(supportedMediaTypes);
public MediaType getDefaultContentType() {
return defaultContentType;
}
/**
* Sets the {@code Content-Type} to be used for writing.
* Sets the default {@code Content-Type} to be used for writing.
*
* @throws IllegalArgumentException if the given content type is not supported by the Java Image I/O API
*/
public void setContentType(MediaType contentType) {
Assert.notNull(contentType, "'contentType' must not be null");
Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(contentType.toString());
public void setDefaultContentType(MediaType defaultContentType) {
Assert.notNull(defaultContentType, "'contentType' must not be null");
Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(defaultContentType.toString());
if (!imageWriters.hasNext()) {
throw new IllegalArgumentException(
"ContentType [" + contentType + "] is not supported by the Java Image I/O API");
"ContentType [" + defaultContentType + "] is not supported by the Java Image I/O API");
}
this.contentType = contentType;
this.defaultContentType = defaultContentType;
}
/** Sets the cache directory. If this property is set to an existing directory, this converter will cache image data. */
@@ -120,12 +113,46 @@ public class BufferedImageHttpMessageConverter extends AbstractHttpMessageConver
this.cacheDir = cacheDir;
}
public boolean supports(Class<? extends BufferedImage> clazz) {
return BufferedImage.class.equals(clazz);
public boolean canRead(Class<? extends BufferedImage> clazz, MediaType mediaType) {
if (BufferedImage.class.equals(clazz)) {
return isReadable(mediaType);
}
else {
return false;
}
}
@Override
public BufferedImage readInternal(Class<BufferedImage> clazz, HttpInputMessage inputMessage) throws IOException {
private boolean isReadable(MediaType mediaType) {
if (mediaType == null) {
return true;
}
Iterator<ImageReader> imageReaders = ImageIO.getImageReadersByMIMEType(mediaType.toString());
return imageReaders.hasNext();
}
public boolean canWrite(Class<? extends BufferedImage> clazz, MediaType mediaType) {
if (BufferedImage.class.equals(clazz)) {
return isWritable(mediaType);
}
else {
return false;
}
}
private boolean isWritable(MediaType mediaType) {
if (mediaType == null) {
return true;
}
Iterator<ImageWriter> imageWriters = ImageIO.getImageWritersByMIMEType(mediaType.toString());
return imageWriters.hasNext();
}
public List<MediaType> getSupportedMediaTypes() {
return Collections.unmodifiableList(readableMediaTypes);
}
public BufferedImage read(Class<BufferedImage> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
ImageInputStream imageInputStream = null;
ImageReader imageReader = null;
try {
@@ -168,13 +195,14 @@ public class BufferedImageHttpMessageConverter extends AbstractHttpMessageConver
}
}
@Override
protected MediaType getContentType(BufferedImage image) {
return contentType;
}
@Override
protected void writeInternal(BufferedImage image, HttpOutputMessage outputMessage) throws IOException {
public void write(BufferedImage image, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
if (contentType == null) {
contentType = getDefaultContentType();
}
Assert.notNull(contentType,
"Count not determine Content-Type, set one using the 'defaultContentType' property");
outputMessage.getHeaders().setContentType(contentType);
ImageOutputStream imageOutputStream = null;
ImageWriter imageWriter = null;
try {

View File

@@ -29,8 +29,7 @@ import org.springframework.util.FileCopyUtils;
*
* <p>By default, this converter supports all media types (<code>&#42;&#47;&#42;</code>), and writes with a {@code
* Content-Type} of {@code application/octet-stream}. This can be overridden by setting the {@link
* #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property, and overridding {@link
* #getContentType(byte[])}.
* #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
*
* @author Arjen Poutsma
* @since 3.0
@@ -39,9 +38,10 @@ public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<
/** Creates a new instance of the {@code ByteArrayHttpMessageConverter}. */
public ByteArrayHttpMessageConverter() {
super(MediaType.ALL);
super(new MediaType("application", "octet-stream"), MediaType.ALL);
}
@Override
public boolean supports(Class<? extends byte[]> clazz) {
return byte[].class.equals(clazz);
}
@@ -60,12 +60,7 @@ public class ByteArrayHttpMessageConverter extends AbstractHttpMessageConverter<
}
@Override
protected MediaType getContentType(byte[] bytes) {
return new MediaType("application", "octet-stream");
}
@Override
protected Long getContentLength(byte[] bytes) {
protected Long getContentLength(byte[] bytes, MediaType contentType) {
return (long) bytes.length;
}

View File

@@ -55,6 +55,7 @@ public class FormHttpMessageConverter extends AbstractHttpMessageConverter<Multi
super(new MediaType("application", "x-www-form-urlencoded"));
}
@Override
public boolean supports(Class<? extends MultiValueMap<String, String>> clazz) {
return MultiValueMap.class.isAssignableFrom(clazz);
}
@@ -87,7 +88,7 @@ public class FormHttpMessageConverter extends AbstractHttpMessageConverter<Multi
@Override
protected void writeInternal(MultiValueMap<String, String> form, HttpOutputMessage outputMessage)
throws IOException {
MediaType contentType = getContentType(form);
MediaType contentType = getDefaultContentType(form);
Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
StringBuilder builder = new StringBuilder();
for (Iterator<Map.Entry<String, List<String>>> entryIterator = form.entrySet().iterator();

View File

@@ -32,20 +32,35 @@ import org.springframework.http.MediaType;
public interface HttpMessageConverter<T> {
/**
* Indicate whether the given class is supported by this converter.
* Indicates whether the given class can be read by this converter.
*
* @param clazz the class to test for support
* @return <code>true</code> if supported; <code>false</code> otherwise
* @param clazz the class to test for readability
* @param mediaType the media type to read, can be {@code null} if not specified
* @return <code>true</code> if readable; <code>false</code> otherwise
*/
boolean supports(Class<? extends T> clazz);
boolean canRead(Class<? extends T> clazz, MediaType mediaType);
/** Return the list of {@link MediaType} objects supported by this converter. */
/**
* Indicates whether the given class can be written by this converter.
*
* @param clazz the class to test for writability
* @param mediaType the media type to write, can be {@code null} if not specified
* @return <code>true</code> if writable; <code>false</code> otherwise
*/
boolean canWrite(Class<? extends T> clazz, MediaType mediaType);
/**
* Return the list of {@link MediaType} objects supported by this converter.
*
* @return the list of supported media types
*/
List<MediaType> getSupportedMediaTypes();
/**
* Read an object of the given type form the given input message, and returns it.
*
* @param clazz the type of object to return
* @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 inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
@@ -56,11 +71,16 @@ public interface HttpMessageConverter<T> {
/**
* Write an given object to the given output message.
*
* @param t the object to write to the output message
* @param t the object to write to the output message. The type of this object must have previously been passed to the
* {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param contentType the content type to use when writing. May be {@code null} to indicate that the the default
* content type of the converter must be used. If not {@code null}, this media type must have previously been passed to
* the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
* @param outputMessage the message to write to
* @throws IOException in case of I/O errors
* @throws HttpMessageNotWritableException in case of conversion errors
*/
void write(T t, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException;
}

View File

@@ -32,7 +32,7 @@ import org.springframework.util.FileCopyUtils;
/**
* Implementation of {@link HttpMessageConverter} that can read and write strings.
*
* <p>By default, this converter supports all text media types (<code>text&#47;&#42;</code>), and writes with a {@code
* <p>By default, this converter supports all media types (<code>&#42;&#47;&#42;</code>), and writes with a {@code
* Content-Type} of {@code text/plain}. This can be overridden by setting the {@link
* #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
*
@@ -46,10 +46,11 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
private final List<Charset> availableCharsets;
public StringHttpMessageConverter() {
super(new MediaType("text", "plain", DEFAULT_CHARSET), new MediaType("text", "*"));
super(new MediaType("text", "plain", DEFAULT_CHARSET), MediaType.ALL);
this.availableCharsets = new ArrayList<Charset>(Charset.availableCharsets().values());
}
@Override
public boolean supports(Class<? extends String> clazz) {
return String.class.equals(clazz);
}
@@ -62,9 +63,9 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
}
@Override
protected Long getContentLength(String s) {
Charset charset = getContentType(s).getCharSet();
if (charset != null) {
protected Long getContentLength(String s, MediaType contentType) {
if (contentType != null && contentType.getCharSet() != null) {
Charset charset = contentType.getCharSet();
try {
return (long) s.getBytes(charset.name()).length;
}
@@ -81,14 +82,15 @@ public class StringHttpMessageConverter extends AbstractHttpMessageConverter<Str
@Override
protected void writeInternal(String s, HttpOutputMessage outputMessage) throws IOException {
outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets());
MediaType contentType = getContentType(s);
MediaType contentType = outputMessage.getHeaders().getContentType();
Charset charset = contentType.getCharSet() != null ? contentType.getCharSet() : DEFAULT_CHARSET;
FileCopyUtils.copy(s, new OutputStreamWriter(outputMessage.getBody(), charset));
}
/**
* Return the list of supported {@link Charset}. <p>By default, returns {@link Charset#availableCharsets()}. Can be
* overridden in subclasses.
* Return the list of supported {@link Charset}.
*
* <p>By default, returns {@link Charset#availableCharsets()}. Can be overridden in subclasses.
*
* @return the list of accepted charsets
*/

View File

@@ -54,9 +54,7 @@ public class MappingJacksonHttpMessageConverter<T> extends AbstractHttpMessageCo
private boolean prefixJson = false;
/**
* Construct a new {@code BindingJacksonHttpMessageConverter},
*/
/** Construct a new {@code BindingJacksonHttpMessageConverter}, */
public MappingJacksonHttpMessageConverter() {
super(new MediaType("application", "json"));
}
@@ -75,9 +73,7 @@ public class MappingJacksonHttpMessageConverter<T> extends AbstractHttpMessageCo
this.objectMapper = objectMapper;
}
/**
* Sets the {@code JsonEncoding} for this converter. By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used.
*/
/** Sets the {@code JsonEncoding} for this converter. By default, {@linkplain JsonEncoding#UTF8 UTF-8} is used. */
public void setEncoding(JsonEncoding encoding) {
Assert.notNull(encoding, "'encoding' must not be null");
this.encoding = encoding;
@@ -94,6 +90,7 @@ public class MappingJacksonHttpMessageConverter<T> extends AbstractHttpMessageCo
this.prefixJson = prefixJson;
}
@Override
public boolean supports(Class<? extends T> clazz) {
return objectMapper.canSerialize(clazz);
}
@@ -105,7 +102,7 @@ public class MappingJacksonHttpMessageConverter<T> extends AbstractHttpMessageCo
}
@Override
protected MediaType getContentType(T t) {
protected MediaType getDefaultContentType(T t) {
Charset charset = Charset.forName(encoding.getJavaName());
return new MediaType("application", "json", charset);
}

View File

@@ -36,8 +36,9 @@ import org.springframework.http.converter.HttpMessageConversionException;
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters} that
* convert from/to XML.
*
* <p>By default, subclasses of this converter support {@code text/xml} and {@code application/xml}. This can be
* overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property.
* <p>By default, subclasses of this converter support {@code text/xml}, {@code application/xml}, and {@code
* application/*-xml}. This can be overridden by setting the {@link #setSupportedMediaTypes(java.util.List)
* supportedMediaTypes} property.
*
* @author Arjen Poutsma
* @since 3.0
@@ -48,10 +49,10 @@ public abstract class AbstractXmlHttpMessageConverter<T> extends AbstractHttpMes
/**
* Protected constructor that sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} to {@code
* text/xml} and {@code application/xml}.
* text/xml} and {@code application/xml}, and {@code application/*-xml}.
*/
protected AbstractXmlHttpMessageConverter() {
super(new MediaType("application", "xml"), new MediaType("text", "xml"));
super(new MediaType("application", "xml"), new MediaType("text", "xml"), new MediaType("application", "*+xml"));
}
/** Invokes {@link #readFromSource(Class, HttpHeaders, Source)}. */

View File

@@ -35,11 +35,19 @@ import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
/** @author Arjen Poutsma */
/**
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write {@link
* Source} objects.
*
* @author Arjen Poutsma
* @since 3.0
*/
public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHttpMessageConverter<T> {
@Override
public boolean supports(Class<? extends T> clazz) {
return Source.class.isAssignableFrom(clazz);
return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) || StreamSource.class.equals(clazz) ||
Source.class.equals(clazz);
}
@Override
@@ -52,11 +60,11 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHtt
return (T) new DOMSource(domResult.getNode());
}
else if (SAXSource.class.equals(clazz)) {
ByteArrayInputStream bis = transformToByteArray(source);
ByteArrayInputStream bis = transformToByteArrayInputStream(source);
return (T) new SAXSource(new InputSource(bis));
}
else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
ByteArrayInputStream bis = transformToByteArray(source);
ByteArrayInputStream bis = transformToByteArrayInputStream(source);
return (T) new StreamSource(bis);
}
else {
@@ -65,11 +73,12 @@ public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHtt
}
}
catch (TransformerException ex) {
throw new HttpMessageNotReadableException("Could not transform from [" + source + "]", ex);
throw new HttpMessageNotReadableException("Could not transform from [" + source + "] to [" + clazz + "]",
ex);
}
}
private ByteArrayInputStream transformToByteArray(Source source) throws TransformerException {
private ByteArrayInputStream transformToByteArrayInputStream(Source source) throws TransformerException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
transform(source, new StreamResult(bos));
return new ByteArrayInputStream(bos.toByteArray());

View File

@@ -23,11 +23,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.LinkedHashMap;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -40,17 +40,17 @@ import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.HttpMediaTypeNotSupportedException;
@@ -73,16 +73,16 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
/**
* Support class for invoking an annotated handler method. Operates on the introspection results of a
* {@link HandlerMethodResolver} for a specific handler type.
* Support class for invoking an annotated handler method. Operates on the introspection results of a {@link
* HandlerMethodResolver} for a specific handler type.
*
* <p>Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} and
* {@link org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
* <p>Used by {@link org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter} and {@link
* org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter}.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.5.2
* @see #invokeHandlerMethod
* @since 2.5.2
*/
public class HandlerMethodInvoker {
@@ -103,7 +103,6 @@ public class HandlerMethodInvoker {
private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
public HandlerMethodInvoker(HandlerMethodResolver methodResolver) {
this(methodResolver, null);
}
@@ -112,9 +111,12 @@ public class HandlerMethodInvoker {
this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, null, null);
}
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer,
SessionAttributeStore sessionAttributeStore, ParameterNameDiscoverer parameterNameDiscoverer,
WebArgumentResolver[] customArgumentResolvers, HttpMessageConverter[] messageConverters) {
public HandlerMethodInvoker(HandlerMethodResolver methodResolver,
WebBindingInitializer bindingInitializer,
SessionAttributeStore sessionAttributeStore,
ParameterNameDiscoverer parameterNameDiscoverer,
WebArgumentResolver[] customArgumentResolvers,
HttpMessageConverter[] messageConverters) {
this.methodResolver = methodResolver;
this.bindingInitializer = bindingInitializer;
@@ -124,9 +126,10 @@ public class HandlerMethodInvoker {
this.messageConverters = messageConverters;
}
public final Object invokeHandlerMethod(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
public final Object invokeHandlerMethod(Method handlerMethod,
Object handler,
NativeWebRequest webRequest,
ExtendedModelMap implicitModel) throws Exception {
Method handlerMethodToInvoke = BridgeMethodResolver.findBridgedMethod(handlerMethod);
try {
@@ -149,10 +152,10 @@ public class HandlerMethodInvoker {
}
Object attrValue = doInvokeMethod(attributeMethodToInvoke, handler, args);
if ("".equals(attrName)) {
Class resolvedType = GenericTypeResolver.resolveReturnType(
attributeMethodToInvoke, handler.getClass());
attrName = Conventions.getVariableNameForReturnType(
attributeMethodToInvoke, resolvedType, attrValue);
Class resolvedType =
GenericTypeResolver.resolveReturnType(attributeMethodToInvoke, handler.getClass());
attrName =
Conventions.getVariableNameForReturnType(attributeMethodToInvoke, resolvedType, attrValue);
}
if (!implicitModel.containsAttribute(attrName)) {
implicitModel.addAttribute(attrName, attrValue);
@@ -171,8 +174,10 @@ public class HandlerMethodInvoker {
}
@SuppressWarnings("unchecked")
private Object[] resolveHandlerArguments(Method handlerMethod, Object handler,
NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception {
private Object[] resolveHandlerArguments(Method handlerMethod,
Object handler,
NativeWebRequest webRequest,
ExtendedModelMap implicitModel) throws Exception {
Class[] paramTypes = handlerMethod.getParameterTypes();
Object[] args = new Object[paramTypes.length];
@@ -287,7 +292,8 @@ public class HandlerMethodInvoker {
args[i] = resolvePathVariable(pathVarName, methodParam, webRequest, handler);
}
else if (attrName != null) {
WebRequestDataBinder binder = resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
WebRequestDataBinder binder =
resolveModelAttribute(attrName, methodParam, implicitModel, webRequest, handler);
boolean assignBindingResult = (args.length > i + 1 && Errors.class.isAssignableFrom(paramTypes[i + 1]));
if (binder.getTarget() != null) {
doBind(binder, webRequest, validate, !assignBindingResult);
@@ -334,8 +340,10 @@ public class HandlerMethodInvoker {
}
}
private Object[] resolveInitBinderArguments(Object handler, Method initBinderMethod,
WebDataBinder binder, NativeWebRequest webRequest) throws Exception {
private Object[] resolveInitBinderArguments(Object handler,
Method initBinderMethod,
WebDataBinder binder,
NativeWebRequest webRequest) throws Exception {
Class[] initBinderParams = initBinderMethod.getParameterTypes();
Object[] initBinderArgs = new Object[initBinderParams.length];
@@ -390,8 +398,8 @@ public class HandlerMethodInvoker {
}
if (paramName != null) {
initBinderArgs[i] = resolveRequestParam(
paramName, paramRequired, paramDefaultValue, methodParam, webRequest, null);
initBinderArgs[i] =
resolveRequestParam(paramName, paramRequired, paramDefaultValue, methodParam, webRequest, null);
}
else if (pathVarName != null) {
initBinderArgs[i] = resolvePathVariable(pathVarName, methodParam, webRequest, null);
@@ -402,9 +410,12 @@ public class HandlerMethodInvoker {
}
@SuppressWarnings("unchecked")
private Object resolveRequestParam(String paramName, boolean required, String defaultValue,
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
throws Exception {
private Object resolveRequestParam(String paramName,
boolean required,
String defaultValue,
MethodParameter methodParam,
NativeWebRequest webRequest,
Object handlerForInitBinderCall) throws Exception {
Class<?> paramType = methodParam.getParameterType();
if (Map.class.isAssignableFrom(paramType)) {
@@ -460,9 +471,12 @@ public class HandlerMethodInvoker {
}
@SuppressWarnings("unchecked")
private Object resolveRequestHeader(String headerName, boolean required, String defaultValue,
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
throws Exception {
private Object resolveRequestHeader(String headerName,
boolean required,
String defaultValue,
MethodParameter methodParam,
NativeWebRequest webRequest,
Object handlerForInitBinderCall) throws Exception {
Class<?> paramType = methodParam.getParameterType();
if (Map.class.isAssignableFrom(paramType)) {
@@ -495,7 +509,8 @@ public class HandlerMethodInvoker {
MultiValueMap<String, String> result;
if (HttpHeaders.class.isAssignableFrom(mapType)) {
result = new HttpHeaders();
} else {
}
else {
result = new LinkedMultiValueMap<String, String>();
}
for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
@@ -517,10 +532,7 @@ public class HandlerMethodInvoker {
}
}
/**
* Resolves the given {@link RequestBody @RequestBody} annotation.
*/
/** Resolves the given {@link RequestBody @RequestBody} annotation. */
@SuppressWarnings("unchecked")
protected Object resolveRequestBody(MethodParameter methodParam, NativeWebRequest webRequest, Object handler)
throws Exception {
@@ -542,12 +554,8 @@ public class HandlerMethodInvoker {
if (this.messageConverters != null) {
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
if (messageConverter.supports(paramType)) {
for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) {
if (supportedMediaType.includes(contentType)) {
return messageConverter.read(paramType, inputMessage);
}
}
if (messageConverter.canRead(paramType, contentType)) {
return messageConverter.read(paramType, inputMessage);
}
}
}
@@ -555,16 +563,19 @@ public class HandlerMethodInvoker {
}
/**
* Return a {@link HttpInputMessage} for the given {@link NativeWebRequest}.
* Throws an UnsupportedOperationException by default.
* Return a {@link HttpInputMessage} for the given {@link NativeWebRequest}. Throws an UnsupportedOperationException by
* default.
*/
protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception {
throw new UnsupportedOperationException("@RequestBody not supported");
}
private Object resolveCookieValue(String cookieName, boolean required, String defaultValue,
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
throws Exception {
private Object resolveCookieValue(String cookieName,
boolean required,
String defaultValue,
MethodParameter methodParam,
NativeWebRequest webRequest,
Object handlerForInitBinderCall) throws Exception {
Class<?> paramType = methodParam.getParameterType();
if (cookieName.length() == 0) {
@@ -585,18 +596,17 @@ public class HandlerMethodInvoker {
return binder.convertIfNecessary(cookieValue, paramType, methodParam);
}
/**
* Resolves the given {@link CookieValue @CookieValue} annotation.
* Throws an UnsupportedOperationException by default.
*/
/** Resolves the given {@link CookieValue @CookieValue} annotation. Throws an UnsupportedOperationException by default. */
protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest)
throws Exception {
throw new UnsupportedOperationException("@CookieValue not supported");
}
private Object resolvePathVariable(String pathVarName, MethodParameter methodParam,
NativeWebRequest webRequest, Object handlerForInitBinderCall) throws Exception {
private Object resolvePathVariable(String pathVarName,
MethodParameter methodParam,
NativeWebRequest webRequest,
Object handlerForInitBinderCall) throws Exception {
Class<?> paramType = methodParam.getParameterType();
if (pathVarName.length() == 0) {
@@ -609,8 +619,8 @@ public class HandlerMethodInvoker {
}
/**
* Resolves the given {@link PathVariable @PathVariable} annotation.
* Throws an UnsupportedOperationException by default.
* Resolves the given {@link PathVariable @PathVariable} annotation. Throws an UnsupportedOperationException by
* default.
*/
protected String resolvePathVariable(String pathVarName, Class paramType, NativeWebRequest webRequest)
throws Exception {
@@ -621,9 +631,9 @@ public class HandlerMethodInvoker {
private String getRequiredParameterName(MethodParameter methodParam) {
String name = methodParam.getParameterName();
if (name == null) {
throw new IllegalStateException("No parameter name specified for argument of type [" +
methodParam.getParameterType().getName() +
"], and no parameter name information found in class file either.");
throw new IllegalStateException(
"No parameter name specified for argument of type [" + methodParam.getParameterType().getName() +
"], and no parameter name information found in class file either.");
}
return name;
}
@@ -642,9 +652,11 @@ public class HandlerMethodInvoker {
return value;
}
private WebRequestDataBinder resolveModelAttribute(String attrName, MethodParameter methodParam,
ExtendedModelMap implicitModel, NativeWebRequest webRequest, Object handler)
throws Exception {
private WebRequestDataBinder resolveModelAttribute(String attrName,
MethodParameter methodParam,
ExtendedModelMap implicitModel,
NativeWebRequest webRequest,
Object handler) throws Exception {
// Bind request parameter onto object...
String name = attrName;
@@ -671,8 +683,10 @@ public class HandlerMethodInvoker {
}
@SuppressWarnings("unchecked")
public final void updateModelAttributes(Object handler, Map<String, Object> mavModel,
ExtendedModelMap implicitModel, NativeWebRequest webRequest) throws Exception {
public final void updateModelAttributes(Object handler,
Map<String, Object> mavModel,
ExtendedModelMap implicitModel,
NativeWebRequest webRequest) throws Exception {
if (this.methodResolver.hasSessionAttributes() && this.sessionStatus.isComplete()) {
for (String attrName : this.methodResolver.getActualSessionAttributeNames()) {
@@ -731,15 +745,18 @@ public class HandlerMethodInvoker {
}
protected void raiseMissingCookieException(String cookieName, Class paramType) throws Exception {
throw new IllegalStateException("Missing cookie value '" + cookieName + "' of type [" + paramType.getName() + "]");
throw new IllegalStateException(
"Missing cookie value '" + cookieName + "' of type [" + paramType.getName() + "]");
}
protected void raiseSessionRequiredException(String message) throws Exception {
throw new IllegalStateException(message);
}
protected void doBind(WebRequestDataBinder binder, NativeWebRequest webRequest, boolean validate, boolean failOnErrors)
throws Exception {
protected void doBind(WebRequestDataBinder binder,
NativeWebRequest webRequest,
boolean validate,
boolean failOnErrors) throws Exception {
binder.bind(webRequest);
if (validate) {
@@ -785,9 +802,11 @@ public class HandlerMethodInvoker {
}
return WebArgumentResolver.UNRESOLVED;
}
protected final void addReturnValueAsModelAttribute(
Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel) {
protected final void addReturnValueAsModelAttribute(Method handlerMethod,
Class handlerType,
Object returnValue,
ExtendedModelMap implicitModel) {
ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class);
String attrName = (attr != null ? attr.value() : "");

View File

@@ -36,29 +36,28 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
private final Class<T> responseType;
private final List<HttpMessageConverter<T>> messageConverters;
private final List<HttpMessageConverter<?>> 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(Class<T> responseType, List<HttpMessageConverter<T>> messageConverters) {
public HttpMessageConverterExtractor(Class<T> responseType, List<HttpMessageConverter<?>> messageConverters) {
Assert.notNull(responseType, "'responseType' must not be null");
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.responseType = responseType;
this.messageConverters = messageConverters;
}
@SuppressWarnings("unchecked")
public T extractData(ClientHttpResponse response) throws IOException {
MediaType contentType = response.getHeaders().getContentType();
if (contentType == null) {
throw new RestClientException("Cannot extract response: no Content-Type found");
}
for (HttpMessageConverter<T> messageConverter : messageConverters) {
for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) {
if (supportedMediaType.includes(contentType)) {
return messageConverter.read(this.responseType, response);
}
for (HttpMessageConverter messageConverter : messageConverters) {
if (messageConverter.canRead(responseType, contentType)) {
return (T) messageConverter.read(this.responseType, response);
}
}
throw new RestClientException(

View File

@@ -44,22 +44,18 @@ import org.springframework.web.util.UriTemplate;
* enforces RESTful principles. It handles HTTP connections, leaving application code to provide URLs (with possible
* template variables) and extract results.
*
* <p>The main entry points of this template are the methods named after the six main HTTP methods:
* <table> <tr><th>HTTP
* <p>The main entry points of this template are the methods named after the six main HTTP methods: <table> <tr><th>HTTP
* method</th><th>RestTemplate methods</th></tr> <tr><td>DELETE</td><td>{@link #delete}</td></tr>
* <tr><td>GET</td><td>{@link #getForObject}</td></tr>
* <tr><td>HEAD</td><td>{@link #headForHeaders}</td></tr>
* <tr><td>OPTIONS</td><td>{@link #optionsForAllow}</td></tr>
* <tr><td>POST</td><td>{@link #postForLocation}</td></tr>
* <tr><td></td><td>{@link #postForObject}</td></tr>
* <tr><td>PUT</td><td>{@link #put}</td></tr> <tr><td>any</td><td>{@link #execute}</td></tr> </table>
* <tr><td>GET</td><td>{@link #getForObject}</td></tr> <tr><td>HEAD</td><td>{@link #headForHeaders}</td></tr>
* <tr><td>OPTIONS</td><td>{@link #optionsForAllow}</td></tr> <tr><td>POST</td><td>{@link #postForLocation}</td></tr>
* <tr><td></td><td>{@link #postForObject}</td></tr> <tr><td>PUT</td><td>{@link #put}</td></tr>
* <tr><td>any</td><td>{@link #execute}</td></tr> </table>
*
* <p>For each of these HTTP methods, there are three corresponding Java methods in the {@code RestTemplate}.
* Two variant take a {@code String} URI as first argument (eg. {@link #getForObject(String, Class, String[])},
* {@link #getForObject(String, Class, Map)}), and are capable of substituting any
* {@linkplain UriTemplate URI templates} in that URL using either a
* {@code String} variable arguments array, or a {@code Map<String, String>}. The string varargs variant expands the
* given template variables in order, so that
* <p>For each of these HTTP methods, there are three corresponding Java methods in the {@code RestTemplate}. Two
* variant take a {@code String} URI as first argument (eg. {@link #getForObject(String, Class, String[])}, {@link
* #getForObject(String, Class, Map)}), and are capable of substituting any {@linkplain UriTemplate URI templates} in
* that URL using either a {@code String} variable arguments array, or a {@code Map<String, String>}. The string varargs
* variant expands the given template variables in order, so that
* <pre>
* String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42",
* "21");
@@ -71,22 +67,22 @@ import org.springframework.web.util.UriTemplate;
* Map&lt;String, String&gt; vars = Collections.singletonMap("hotel", "42");
* String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
* </pre>
* will perform a GET on {@code http://example.com/hotels/42/rooms/42}.
* Alternatively, there are {@link URI} variant methods ({@link #getForObject(URI, Class)}), which do not allow for
* URI templates, but allow you to reuse a single, expanded URI multiple times.
* will perform a GET on {@code http://example.com/hotels/42/rooms/42}. Alternatively, there are {@link URI} variant
* methods ({@link #getForObject(URI, Class)}), which do not allow for URI templates, but allow you to reuse a single,
* expanded URI multiple times.
*
* <p>Furthermore, the {@code String}-argument methods assume that the URL String is unencoded. This means that
* <pre>
* restTemplate.getForObject("http://example.com/hotel list");
* </pre>
* will perform a GET on {@code http://example.com/hotel%20list}. As a result, any URL passed that is already encoded
* will be encoded twice (i.e. {@code http://example.com/hotel%20list} will become {@code http://example.com/hotel%2520list}).
* If this behavior is undesirable, use the {@code URI}-argument methods, which will not perform any URL encoding.
* will be encoded twice (i.e. {@code http://example.com/hotel%20list} will become {@code
* http://example.com/hotel%2520list}). If this behavior is undesirable, use the {@code URI}-argument methods, which
* will not perform any URL encoding.
*
* <p>Objects passed to and returned from these methods are converted to and from HTTP messages by {@link
* HttpMessageConverter} instances. Converters for the main mime types are registered by default, but you can also write
* your own converter and register it via the {@link #setMessageConverters(HttpMessageConverter[]) messageConverters}
* bean property.
* your own converter and register it via the {@link #setMessageConverters messageConverters} bean property.
*
* <p>This template uses a {@link org.springframework.http.client.SimpleClientHttpRequestFactory} and a {@link
* DefaultResponseErrorHandler} as default strategies for creating HTTP connections or handling HTTP errors,
@@ -104,14 +100,16 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
private final ResponseExtractor<HttpHeaders> headersExtractor = new HeadersExtractor();
private HttpMessageConverter<?>[] messageConverters =
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
private List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
/** Create a new instance of the {@link RestTemplate} using default settings. */
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new FormHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter());
}
/**
@@ -122,6 +120,7 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
* @see org.springframework.http.client.CommonsClientHttpRequestFactory
*/
public RestTemplate(ClientHttpRequestFactory requestFactory) {
this();
setRequestFactory(requestFactory);
}
@@ -129,34 +128,16 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
* Set the message body converters to use. These converters are used to convert from and to HTTP requests and
* responses.
*/
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
this.messageConverters = messageConverters;
}
/** Returns the message body converters. These converters are used to convert from and to HTTP requests and responses. */
public HttpMessageConverter<?>[] getMessageConverters() {
public List<HttpMessageConverter<?>> getMessageConverters() {
return this.messageConverters;
}
/**
* Returns the message body converters that support a particular type.
*
* @param type the type to return converters for
* @return converters that support the given type
*/
@SuppressWarnings("unchecked")
protected <T> List<HttpMessageConverter<T>> getSupportedMessageConverters(Class<T> type) {
HttpMessageConverter[] converters = getMessageConverters();
List<HttpMessageConverter<T>> result = new ArrayList<HttpMessageConverter<T>>(converters.length);
for (HttpMessageConverter converter : converters) {
if (converter.supports(type)) {
result.add((HttpMessageConverter<T>) converter);
}
}
return result;
}
/** Set the error handler. */
public void setErrorHandler(ResponseErrorHandler errorHandler) {
Assert.notNull(errorHandler, "'errorHandler' must not be null");
@@ -171,27 +152,25 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
// GET
public <T> T getForObject(String url, Class<T> responseType, String... urlVariables) throws RestClientException {
checkForSupportedMessageConverter(responseType);
List<HttpMessageConverter<T>> supportedMessageConverters = getSupportedMessageConverters(responseType);
return execute(url, HttpMethod.GET, new AcceptHeaderRequestCallback<T>(supportedMessageConverters),
new HttpMessageConverterExtractor<T>(responseType, supportedMessageConverters), urlVariables);
AcceptHeaderRequestCallback<T> requestCallback = new AcceptHeaderRequestCallback<T>(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);
}
public <T> T getForObject(String url, Class<T> responseType, Map<String, String> urlVariables)
throws RestClientException {
checkForSupportedMessageConverter(responseType);
List<HttpMessageConverter<T>> supportedMessageConverters = getSupportedMessageConverters(responseType);
return execute(url, HttpMethod.GET, new AcceptHeaderRequestCallback<T>(supportedMessageConverters),
new HttpMessageConverterExtractor<T>(responseType, supportedMessageConverters), urlVariables);
AcceptHeaderRequestCallback<T> requestCallback = new AcceptHeaderRequestCallback<T>(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);
}
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
checkForSupportedMessageConverter(responseType);
List<HttpMessageConverter<T>> supportedMessageConverters = getSupportedMessageConverters(responseType);
return execute(url, HttpMethod.GET, new AcceptHeaderRequestCallback<T>(supportedMessageConverters),
new HttpMessageConverterExtractor<T>(responseType, supportedMessageConverters));
AcceptHeaderRequestCallback<T> requestCallback = new AcceptHeaderRequestCallback<T>(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}
// HEAD
@@ -211,86 +190,62 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
// POST
public URI postForLocation(String url, Object request, String... urlVariables) throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
HttpHeaders headers =
execute(url, HttpMethod.POST, new PostPutCallback(request), this.headersExtractor, urlVariables);
PostPutCallback requestCallback = new PostPutCallback(request);
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables);
return headers.getLocation();
}
public URI postForLocation(String url, Object request, Map<String, String> urlVariables)
throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
HttpHeaders headers =
execute(url, HttpMethod.POST, new PostPutCallback(request), this.headersExtractor, urlVariables);
PostPutCallback requestCallback = new PostPutCallback(request);
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables);
return headers.getLocation();
}
public URI postForLocation(URI url, Object request)
throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
HttpHeaders headers = execute(url, HttpMethod.POST, new PostPutCallback(request), this.headersExtractor);
public URI postForLocation(URI url, Object request) throws RestClientException {
PostPutCallback requestCallback = new PostPutCallback(request);
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor);
return headers.getLocation();
}
public <T> T postForObject(String url, Object request, Class<T> responseType, String... uriVariables)
throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
checkForSupportedMessageConverter(responseType);
List<HttpMessageConverter<T>> responseMessageConverters = getSupportedMessageConverters(responseType);
return execute(url, HttpMethod.POST, new PostPutCallback<T>(request, responseMessageConverters),
new HttpMessageConverterExtractor<T>(responseType, responseMessageConverters), uriVariables);
PostPutCallback<T> requestCallback = new PostPutCallback<T>(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, String> uriVariables)
throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
checkForSupportedMessageConverter(responseType);
List<HttpMessageConverter<T>> responseMessageConverters = getSupportedMessageConverters(responseType);
return execute(url, HttpMethod.POST, new PostPutCallback<T>(request, responseMessageConverters),
new HttpMessageConverterExtractor<T>(responseType, responseMessageConverters), uriVariables);
PostPutCallback<T> requestCallback = new PostPutCallback<T>(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
checkForSupportedMessageConverter(responseType);
List<HttpMessageConverter<T>> responseMessageConverters = getSupportedMessageConverters(responseType);
return execute(url, HttpMethod.POST, new PostPutCallback<T>(request, responseMessageConverters),
new HttpMessageConverterExtractor<T>(responseType, responseMessageConverters));
PostPutCallback<T> requestCallback = new PostPutCallback<T>(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
// PUT
public void put(String url, Object request, String... urlVariables) throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
execute(url, HttpMethod.PUT, new PostPutCallback(request), null, urlVariables);
PostPutCallback requestCallback = new PostPutCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null, urlVariables);
}
public void put(String url, Object request, Map<String, String> urlVariables) throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
execute(url, HttpMethod.PUT, new PostPutCallback(request), null, urlVariables);
PostPutCallback requestCallback = new PostPutCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null, urlVariables);
}
public void put(URI url, Object request) throws RestClientException {
if (request != null) {
checkForSupportedMessageConverter(request.getClass());
}
execute(url, HttpMethod.PUT, new PostPutCallback(request), null);
PostPutCallback requestCallback = new PostPutCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null);
}
// DELETE
@@ -405,28 +360,12 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
}
}
/**
* Check whether any of the registered {@linkplain #setMessageConverters(HttpMessageConverter[]) message body
* converters} can convert the given type.
*
* @param type the type to check for
* @throws IllegalArgumentException if no supported entity converter can be found
* @see HttpMessageConverter#supports(Class)
*/
private void checkForSupportedMessageConverter(Class type) {
for (HttpMessageConverter<?> entityConverter : getMessageConverters()) {
if (entityConverter.supports(type)) {
return;
}
}
throw new IllegalArgumentException("Could not resolve HttpMessageConverter for [" + type.getName() + "]");
}
private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
if (logger.isDebugEnabled()) {
try {
logger.debug(method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() +
" (" + response.getStatusText() + ")");
logger.debug(
method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" +
response.getStatusText() + ")");
}
catch (IOException e) {
// ignore
@@ -437,8 +376,9 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
private void handleResponseError(HttpMethod method, URI url, ClientHttpResponse response) throws IOException {
if (logger.isWarnEnabled()) {
try {
logger.warn(method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() +
" (" + response.getStatusText() + "); invoking error handler");
logger.warn(
method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" +
response.getStatusText() + "); invoking error handler");
}
catch (IOException e) {
// ignore
@@ -450,27 +390,36 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
/** Request callback implementation that prepares the request's accept headers. */
private class AcceptHeaderRequestCallback<T> implements RequestCallback {
private final List<HttpMessageConverter<T>> messageConverters;
private final Class<T> responseType;
private AcceptHeaderRequestCallback(List<HttpMessageConverter<T>> messageConverters) {
this.messageConverters = messageConverters;
private AcceptHeaderRequestCallback() {
responseType = null;
}
private AcceptHeaderRequestCallback(Class<T> responseType) {
this.responseType = responseType;
}
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest request) throws IOException {
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> entityConverter : messageConverters) {
List<MediaType> supportedMediaTypes = entityConverter.getSupportedMediaTypes();
for (MediaType supportedMediaType : supportedMediaTypes) {
if (supportedMediaType.getCharSet() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
if (responseType != null) {
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);
}
}
allSupportedMediaTypes.add(supportedMediaType);
}
}
if (!allSupportedMediaTypes.isEmpty()) {
Collections.sort(allSupportedMediaTypes);
request.getHeaders().setAccept(allSupportedMediaTypes);
if (!allSupportedMediaTypes.isEmpty()) {
Collections.sort(allSupportedMediaTypes);
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
}
}
@@ -478,25 +427,45 @@ public class RestTemplate extends HttpAccessor implements RestOperations {
/** Request callback implementation that writes the given object to the request stream. */
private class PostPutCallback<T> extends AcceptHeaderRequestCallback<T> {
private final Object request;
private final Object requestBody;
private PostPutCallback(Object request, List<HttpMessageConverter<T>> responseMessageConverters) {
super(responseMessageConverters);
this.request = request;
private final MediaType requestContentType;
private PostPutCallback(Object requestBody) {
this.requestBody = requestBody;
this.requestContentType = null;
}
private PostPutCallback(Object request) {
super(Collections.<HttpMessageConverter<T>>emptyList());
this.request = request;
private PostPutCallback(Object requestBody, Class<T> responseType) {
super(responseType);
this.requestBody = requestBody;
this.requestContentType = null;
}
private PostPutCallback(Object requestBody, MediaType requestContentType, Class<T> responseType) {
super(responseType);
this.requestBody = requestBody;
this.requestContentType = requestContentType;
}
@Override
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
if (request != null) {
HttpMessageConverter entityConverter = getSupportedMessageConverters(this.request.getClass()).get(0);
entityConverter.write(this.request, httpRequest);
if (requestBody != null) {
Class requestType = requestBody.getClass();
for (HttpMessageConverter messageConverter : getMessageConverters()) {
if (messageConverter.canWrite(requestType, requestContentType)) {
messageConverter.write(requestBody, requestContentType, httpRequest);
return;
}
}
String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
requestType.getName() + "]";
if (requestContentType != null) {
message += " and content type [" + requestContentType + "]";
}
throw new RestClientException(message);
}
else {
httpRequest.getHeaders().setContentLength(0L);