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

@ -121,22 +121,24 @@ import org.springframework.web.util.WebUtils;
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @since 2.5
* @see #setPathMatcher
* @see #setMethodNameResolver
* @see #setWebBindingInitializer
* @see #setSessionAttributeStore
* @since 2.5
*/
public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter, BeanFactoryAware {
/**
* Log category to use when no mapped handler is found for a request.
*
* @see #pageNotFoundLogger
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Additional logger to use when no mapped handler is found for a request.
*
* @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
@ -162,7 +164,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
private ModelAndViewResolver[] customModelAndViewResolvers;
private HttpMessageConverter<?>[] messageConverters =
new HttpMessageConverter[] {new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
private ConfigurableBeanFactory beanFactory;
@ -172,17 +174,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>();
public AnnotationMethodHandlerAdapter() {
// no restriction of HTTP methods by default
super(false);
}
/**
* Set if URL lookup should always use the full path within the current servlet context. Else, the path within the
* current servlet mapping is used if applicable (that is, in the case of a ".../*" servlet mapping in web.xml).
* <p>Default is "false".
*
* @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
@ -193,6 +194,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
* Set if context path and request URI should be URL-decoded. Both are returned <i>undecoded</i> by the Servlet API, in
* contrast to the servlet path. <p>Uses either the request encoding or the default encoding according to the Servlet
* spec (ISO-8859-1).
*
* @see org.springframework.web.util.UrlPathHelper#setUrlDecode
*/
public void setUrlDecode(boolean urlDecode) {
@ -211,6 +213,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Set the PathMatcher implementation to use for matching URL paths against registered URL patterns. Default is
* AntPathMatcher.
*
* @see org.springframework.util.AntPathMatcher
*/
public void setPathMatcher(PathMatcher pathMatcher) {
@ -220,8 +223,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Set the MethodNameResolver to use for resolving default handler methods (carrying an empty
* <code>@RequestMapping</code> annotation).
* <p>Will only kick in when the handler method cannot be resolved uniquely
* <code>@RequestMapping</code> annotation). <p>Will only kick in when the handler method cannot be resolved uniquely
* through the annotation metadata already.
*/
public void setMethodNameResolver(MethodNameResolver methodNameResolver) {
@ -229,16 +231,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Specify a WebBindingInitializer which will apply pre-configured configuration to every
* DataBinder that this controller uses.
* Specify a WebBindingInitializer which will apply pre-configured configuration to every DataBinder that this
* controller uses.
*/
public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
this.webBindingInitializer = webBindingInitializer;
}
/**
* Specify the strategy to store session attributes with.
* <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* Specify the strategy to store session attributes with. <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
* storing session attributes in the HttpSession, using the same attribute name as in the model.
*/
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
@ -248,10 +249,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Cache content produced by <code>@SessionAttributes</code> annotated handlers for the given number of seconds.
* Default is 0, preventing caching completely.
* <p>In contrast to the "cacheSeconds" property which will apply to all
* Default is 0, preventing caching completely. <p>In contrast to the "cacheSeconds" property which will apply to all
* general handlers (but not to <code>@SessionAttributes</code> annotated handlers), this setting will apply to
* <code>@SessionAttributes</code> annotated handlers only.
*
* @see #setCacheSeconds
* @see org.springframework.web.bind.annotation.SessionAttributes
*/
@ -260,17 +261,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
/**
* Set if controller execution should be synchronized on the session, to serialize
* parallel invocations from the same client.
* <p>More specifically, the execution of each handler method will get synchronized if this
* flag is "true". The best available session mutex will be used for the synchronization;
* ideally, this will be a mutex exposed by HttpSessionMutexListener.
* <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant.
* It serves as a safe reference to synchronize on for locking on the current session.
* <p>In many cases, the HttpSession reference itself a safe mutex as well, since it will
* always be the same object reference for the same active logical session. However, this is
* not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
* Set if controller execution should be synchronized on the session, to serialize parallel invocations from the same
* client. <p>More specifically, the execution of each handler method will get synchronized if this flag is "true". The
* best available session mutex will be used for the synchronization; ideally, this will be a mutex exposed by
* HttpSessionMutexListener. <p>The session mutex is guaranteed to be the same object during the entire lifetime of the
* session, available under the key defined by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a safe
* reference to synchronize on for locking on the current session. <p>In many cases, the HttpSession reference itself a
* safe mutex as well, since it will always be the same object reference for the same active logical session. However,
* this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
*
* @see org.springframework.web.util.HttpSessionMutexListener
* @see org.springframework.web.util.WebUtils#getSessionMutex(javax.servlet.http.HttpSession)
*/
@ -280,40 +279,41 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Set the ParameterNameDiscoverer to use for resolving method parameter names if needed (e.g. for default attribute
* names).
* <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
* names). <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
*/
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
this.parameterNameDiscoverer = parameterNameDiscoverer;
}
/**
* Set a custom WebArgumentResolvers to use for special method parameter types. Such a custom WebArgumentResolver will kick
* in first, having a chance to resolve an argument value before the standard argument handling kicks in.
* Set a custom WebArgumentResolvers to use for special method parameter types. Such a custom WebArgumentResolver will
* kick in first, having a chance to resolve an argument value before the standard argument handling kicks in.
*/
public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
this.customArgumentResolvers = new WebArgumentResolver[]{argumentResolver};
}
/**
* Set one or more custom WebArgumentResolvers to use for special method parameter types. Any such custom WebArgumentResolver
* will kick in first, having a chance to resolve an argument value before the standard argument handling kicks in.
* Set one or more custom WebArgumentResolvers to use for special method parameter types. Any such custom
* WebArgumentResolver will kick in first, having a chance to resolve an argument value before the standard argument
* handling kicks in.
*/
public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
this.customArgumentResolvers = argumentResolvers;
}
/**
* Set a custom ModelAndViewResolvers to use for special method return types. Such a custom ModelAndViewResolver will kick
* in first, having a chance to resolve an return value before the standard ModelAndView handling kicks in.
* Set a custom ModelAndViewResolvers to use for special method return types. Such a custom ModelAndViewResolver will
* kick in first, having a chance to resolve an return value before the standard ModelAndView handling kicks in.
*/
public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
this.customModelAndViewResolvers = new ModelAndViewResolver[]{customModelAndViewResolver};
}
/**
* Set one or more custom ModelAndViewResolvers to use for special method return types. Any such custom ModelAndViewResolver
* will kick in first, having a chance to resolve an return value before the standard ModelAndView handling kicks in.
* Set one or more custom ModelAndViewResolvers to use for special method return types. Any such custom
* ModelAndViewResolver will kick in first, having a chance to resolve an return value before the standard ModelAndView
* handling kicks in.
*/
public void setCustomModelAndViewResolvers(ModelAndViewResolver[] customModelAndViewResolvers) {
this.customModelAndViewResolvers = customModelAndViewResolvers;
@ -334,7 +334,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
}
@ -386,9 +385,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return -1;
}
/**
* Build a HandlerMethodResolver for the given handler type.
*/
/** Build a HandlerMethodResolver for the given handler type. */
private ServletHandlerMethodResolver getMethodResolver(Object handler) {
Class handlerClass = ClassUtils.getUserClass(handler);
ServletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
@ -399,10 +396,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
return resolver;
}
/**
* Servlet-specific subclass of {@link HandlerMethodResolver}.
*/
/** Servlet-specific subclass of {@link HandlerMethodResolver}. */
private class ServletHandlerMethodResolver extends HandlerMethodResolver {
private ServletHandlerMethodResolver(Class<?> handlerType) {
@ -459,7 +453,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
else {
for (RequestMethod requestMethod : mappingInfo.methods) {
allowedMethods.add(requestMethod.toString());
}
}
}
}
if (match) {
@ -521,17 +515,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
/**
* Determines the matched pattern for the given methodLevelPattern and path.
*
* <p>Uses the following algorithm:
* <ol>
* <li>If there is a type-level mapping with path information, it is
* {@linkplain PathMatcher#combine(String, String) combined} with the method-level pattern.
* <li>If there is a {@linkplain HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern} in the
* request, it is combined with the method-level pattern.
* <li>Otherwise,
* @param methodLevelPattern
* @param lookupPath
* @param request
* @return
* <p>Uses the following algorithm: <ol> <li>If there is a type-level mapping with path information, it is {@linkplain
* PathMatcher#combine(String, String) combined} with the method-level pattern. <li>If there is a {@linkplain
* HandlerMapping#BEST_MATCHING_PATTERN_ATTRIBUTE best matching pattern} in the request, it is combined with the
* method-level pattern. <li>Otherwise,
*/
private String getMatchedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) {
if (hasTypeLevelMapping() && (!ObjectUtils.isEmpty(getTypeLevelMapping().value()))) {
@ -550,7 +537,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
if (StringUtils.hasText(bestMatchingPattern)) {
String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern);
if (!combinedPattern.equals(bestMatchingPattern) && (isPathMatchInternal(combinedPattern, lookupPath))) {
if (!combinedPattern.equals(bestMatchingPattern) &&
(isPathMatchInternal(combinedPattern, lookupPath))) {
return combinedPattern;
}
}
@ -610,10 +598,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
/**
* Servlet-specific subclass of {@link HandlerMethodInvoker}.
*/
/** Servlet-specific subclass of {@link HandlerMethodInvoker}. */
private class ServletHandlerMethodInvoker extends HandlerMethodInvoker {
private boolean responseArgumentUsed = false;
@ -729,7 +714,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
Object returnValue,
ExtendedModelMap implicitModel,
ServletWebRequest webRequest) throws Exception {
ResponseStatus responseStatusAnn = AnnotationUtils.findAnnotation(handlerMethod, ResponseStatus.class);
if (responseStatusAnn != null) {
HttpStatus responseStatus = responseStatusAnn.value();
@ -797,7 +782,8 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
@SuppressWarnings("unchecked")
private void handleResponseBody(Object returnValue, ServletWebRequest webRequest) throws ServletException, IOException {
private void handleResponseBody(Object returnValue, ServletWebRequest webRequest)
throws ServletException, IOException {
HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
if (acceptedMediaTypes.isEmpty()) {
@ -809,16 +795,11 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
if (messageConverters != null) {
for (HttpMessageConverter messageConverter : messageConverters) {
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
if (messageConverter.supports(returnValueType)) {
for (Object o : messageConverter.getSupportedMediaTypes()) {
MediaType supportedMediaType = (MediaType) o;
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (acceptedMediaType.includes(supportedMediaType)) {
messageConverter.write(returnValue, outputMessage);
this.responseArgumentUsed = true;
return;
}
}
for (MediaType acceptedMediaType : acceptedMediaTypes) {
if (messageConverter.canWrite(returnValueType, acceptedMediaType)) {
messageConverter.write(returnValue, null, outputMessage);
this.responseArgumentUsed = true;
return;
}
}
}
@ -827,7 +808,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
static class RequestMappingInfo {
String[] paths = new String[0];
@ -864,7 +844,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
}
}
/**
* Comparator capable of sorting {@link RequestMappingInfo}s (RHIs) so that sorting a list with this comparator will
* result in: <ul> <li>RHIs with {@linkplain RequestMappingInfo#matchedPaths better matched paths} take prescedence

View File

@ -30,12 +30,12 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@ -52,10 +52,10 @@ import org.junit.Test;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.interceptor.SimpleTraceInterceptor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.DerivedTestBean;
import org.springframework.beans.ITestBean;
import org.springframework.beans.TestBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
@ -63,14 +63,16 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.HttpHeaders;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletConfig;
@ -79,9 +81,9 @@ import org.springframework.stereotype.Controller;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.SerializationTestUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
@ -445,7 +447,8 @@ public class ServletAnnotationControllerTests {
DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setBeanFactory(wac.getBeanFactory());
wac.getBeanFactory().addBeanPostProcessor(autoProxyCreator);
wac.getBeanFactory().registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
wac.getBeanFactory()
.registerSingleton("advisor", new DefaultPointcutAdvisor(new SimpleTraceInterceptor()));
wac.refresh();
return wac;
}
@ -639,8 +642,8 @@ public class ServletAnnotationControllerTests {
servlet.service(request, response);
assertEquals("mySurpriseView", response.getContentAsString());
MyParameterDispatchingController deserialized = (MyParameterDispatchingController)
SerializationTestUtils.serializeAndDeserialize(servlet.getWebApplicationContext().getBean("controller"));
MyParameterDispatchingController deserialized = (MyParameterDispatchingController) SerializationTestUtils
.serializeAndDeserialize(servlet.getWebApplicationContext().getBean("controller"));
assertNotNull(deserialized.request);
assertNotNull(deserialized.session);
}
@ -947,7 +950,23 @@ public class ServletAnnotationControllerTests {
@Test
public void responseBodyNoAcceptableMediaType() throws ServletException, IOException {
initServlet(RequestBodyController.class);
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(RequestBodyController.class));
RootBeanDefinition converterDef = new RootBeanDefinition(StringHttpMessageConverter.class);
converterDef.getPropertyValues().add("supportedMediaTypes", new MediaType("text", "plain"));
RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
StringHttpMessageConverter converter = new StringHttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(new MediaType("text", "plain")));
adapterDef.getPropertyValues().add("messageConverters", converter);
wac.registerBeanDefinition("handlerAdapter", adapterDef);
wac.refresh();
return wac;
}
};
servlet.init(new MockServletConfig());
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something");
String requestBody = "Hello World";
@ -975,7 +994,19 @@ public class ServletAnnotationControllerTests {
@Test
public void unsupportedRequestBody() throws ServletException, IOException {
initServlet(RequestBodyController.class);
@SuppressWarnings("serial") DispatcherServlet servlet = new DispatcherServlet() {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller", new RootBeanDefinition(RequestBodyController.class));
RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
adapterDef.getPropertyValues().add("messageConverters", new ByteArrayHttpMessageConverter());
wac.registerBeanDefinition("handlerAdapter", adapterDef);
wac.refresh();
return wac;
}
};
servlet.init(new MockServletConfig());
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/something");
String requestBody = "Hello World";
@ -1061,11 +1092,9 @@ public class ServletAnnotationControllerTests {
@Override
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent) {
GenericWebApplicationContext wac = new GenericWebApplicationContext();
wac.registerBeanDefinition("controller",
new RootBeanDefinition(ModelAndViewResolverController.class));
wac.registerBeanDefinition("controller", new RootBeanDefinition(ModelAndViewResolverController.class));
RootBeanDefinition adapterDef = new RootBeanDefinition(AnnotationMethodHandlerAdapter.class);
adapterDef.getPropertyValues()
.add("customModelAndViewResolver", new MyModelAndViewResolver());
adapterDef.getPropertyValues().add("customModelAndViewResolver", new MyModelAndViewResolver());
wac.registerBeanDefinition("handlerAdapter", adapterDef);
wac.refresh();
return wac;
@ -1086,7 +1115,7 @@ public class ServletAnnotationControllerTests {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
request.setCookies(new Cookie("date", "2008-11-18"));
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("test-108", response.getContentAsString());
}
@ -1096,7 +1125,7 @@ public class ServletAnnotationControllerTests {
initServlet(AmbiguousParamsController.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletResponse response = new MockHttpServletResponse();
servlet.service(request, response);
assertEquals("noParams", response.getContentAsString());
@ -1116,7 +1145,6 @@ public class ServletAnnotationControllerTests {
servlet.service(request, response);
}
@Test
public void requestParamMap() throws Exception {
initServlet(RequestParamMapController.class);
@ -1229,11 +1257,9 @@ public class ServletAnnotationControllerTests {
assertEquals("create", response.getContentAsString());
}
/*
* Controllers
*/
* Controllers
*/
@RequestMapping("/myPath.do")
private static class MyController extends AbstractController {
@ -1470,7 +1496,8 @@ public class ServletAnnotationControllerTests {
@SuppressWarnings("unused")
@ModelAttribute("myCommand")
private ValidTestBean createTestBean(@RequestParam T defaultName,
Map<String, Object> model, @RequestParam Date date) {
Map<String, Object> model,
@RequestParam Date date) {
model.put("myKey", "myOriginalValue");
ValidTestBean tb = new ValidTestBean();
tb.setName(defaultName.getClass().getSimpleName() + ":" + defaultName.toString());
@ -1740,9 +1767,10 @@ public class ServletAnnotationControllerTests {
}
List<TestBean> testBeans = (List<TestBean>) model.get("testBeanList");
if (errors.hasFieldErrors("age")) {
response.getWriter().write(viewName + "-" + tb.getName() + "-" +
errors.getFieldError("age").getCode() + "-" + testBeans.get(0).getName() + "-" +
model.get("myKey") + (model.containsKey("yourKey") ? "-" + model.get("yourKey") : ""));
response.getWriter()
.write(viewName + "-" + tb.getName() + "-" + errors.getFieldError("age").getCode() +
"-" + testBeans.get(0).getName() + "-" + model.get("myKey") +
(model.containsKey("yourKey") ? "-" + model.get("yourKey") : ""));
}
else {
response.getWriter().write(viewName + "-" + tb.getName() + "-" + tb.getAge() + "-" +
@ -1760,6 +1788,7 @@ public class ServletAnnotationControllerTests {
public String getContentType() {
return null;
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) {
request.getSession().setAttribute("model", model);
}
@ -1787,6 +1816,7 @@ public class ServletAnnotationControllerTests {
@Retention(RetentionPolicy.RUNTIME)
@Controller
public @interface MyControllerAnnotation {
}
@MyControllerAnnotation
@ -1835,8 +1865,8 @@ public class ServletAnnotationControllerTests {
@RequestMapping("/myPath.do")
public void myHandle(@RequestParam(value = "id", defaultValue = "${myKey}") String id,
@RequestHeader(defaultValue = "#{systemProperties.myHeader}") String header,
@Value("#{request.contextPath}") String contextPath, HttpServletResponse response)
throws IOException {
@Value("#{request.contextPath}") String contextPath,
HttpServletResponse response) throws IOException {
response.getWriter().write(String.valueOf(id) + "-" + String.valueOf(header) + "-" + contextPath);
}
}
@ -1873,7 +1903,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
public static class PathOrderingController {
@ -1888,7 +1917,6 @@ public class ServletAnnotationControllerTests {
}
}
@Controller
public static class RequestBodyController {
@ -1901,7 +1929,11 @@ public class ServletAnnotationControllerTests {
public static class MyMessageConverter implements HttpMessageConverter {
public boolean supports(Class clazz) {
public boolean canRead(Class clazz, MediaType mediaType) {
return true;
}
public boolean canWrite(Class clazz, MediaType mediaType) {
return true;
}
@ -1914,7 +1946,7 @@ public class ServletAnnotationControllerTests {
throw new HttpMessageNotReadableException("Could not read");
}
public void write(Object o, HttpOutputMessage outputMessage)
public void write(Object o, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
throw new UnsupportedOperationException("Not implemented");
}
@ -1955,8 +1987,11 @@ public class ServletAnnotationControllerTests {
public static class MyModelAndViewResolver implements ModelAndViewResolver {
public ModelAndView resolveModelAndView(Method handlerMethod, Class handlerType,
Object returnValue, ExtendedModelMap implicitModel, NativeWebRequest webRequest) {
public ModelAndView resolveModelAndView(Method handlerMethod,
Class handlerType,
Object returnValue,
ExtendedModelMap implicitModel,
NativeWebRequest webRequest) {
if (returnValue instanceof MySpecialArg) {
return new ModelAndView(new View() {
public String getContentType() {
@ -2002,8 +2037,7 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping(method = RequestMethod.GET)
public void handle(@CookieValue("date") Date date, Writer writer)
throws IOException {
public void handle(@CookieValue("date") Date date, Writer writer) throws IOException {
assertEquals("Invalid path variable value", new Date(108, 10, 18), date);
writer.write("test-" + date.getYear());
}
@ -2043,7 +2077,8 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping("/multiValueMap")
public void multiValueMap(@RequestParam MultiValueMap<String, String> params, Writer writer) throws IOException {
public void multiValueMap(@RequestParam MultiValueMap<String, String> params, Writer writer)
throws IOException {
for (Iterator<Map.Entry<String, List<String>>> it1 = params.entrySet().iterator(); it1.hasNext();) {
Map.Entry<String, List<String>> entry = it1.next();
writer.write(entry.getKey() + "=[");
@ -2078,7 +2113,8 @@ public class ServletAnnotationControllerTests {
}
@RequestMapping("/multiValueMap")
public void multiValueMap(@RequestHeader MultiValueMap<String, String> headers, Writer writer) throws IOException {
public void multiValueMap(@RequestHeader MultiValueMap<String, String> headers, Writer writer)
throws IOException {
for (Iterator<Map.Entry<String, List<String>>> it1 = headers.entrySet().iterator(); it1.hasNext();) {
Map.Entry<String, List<String>> entry = it1.next();
writer.write(entry.getKey() + "=[");
@ -2104,5 +2140,5 @@ public class ServletAnnotationControllerTests {
}
}

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);

View File

@ -42,8 +42,15 @@ public class BufferedImageHttpMessageConverterTests {
}
@Test
public void supports() {
assertTrue("Image not supported", converter.supports(BufferedImage.class));
public void canRead() {
assertTrue("Image not supported", converter.canRead(BufferedImage.class, null));
assertTrue("Image not supported", converter.canRead(BufferedImage.class, new MediaType("image", "png")));
}
@Test
public void canWrite() {
assertTrue("Image not supported", converter.canWrite(BufferedImage.class, null));
assertTrue("Image not supported", converter.canWrite(BufferedImage.class, new MediaType("image", "png")));
}
@Test
@ -60,11 +67,25 @@ public class BufferedImageHttpMessageConverterTests {
@Test
public void write() throws IOException {
Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class);
MediaType contentType = new MediaType("image", "png");
converter.setContentType(contentType);
BufferedImage body = ImageIO.read(logo.getFile());
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(body, outputMessage);
MediaType contentType = new MediaType("image", "png");
converter.write(body, contentType, outputMessage);
assertEquals("Invalid content type", contentType, outputMessage.getHeaders().getContentType());
assertTrue("Invalid size", outputMessage.getBodyAsBytes().length > 0);
BufferedImage result = ImageIO.read(new ByteArrayInputStream(outputMessage.getBodyAsBytes()));
assertEquals("Invalid height", 500, result.getHeight());
assertEquals("Invalid width", 750, result.getWidth());
}
@Test
public void writeDefaultContentType() throws IOException {
Resource logo = new ClassPathResource("logo.jpg", BufferedImageHttpMessageConverterTests.class);
MediaType contentType = new MediaType("image", "png");
converter.setDefaultContentType(contentType);
BufferedImage body = ImageIO.read(logo.getFile());
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(body, contentType, outputMessage);
assertEquals("Invalid content type", contentType, outputMessage.getHeaders().getContentType());
assertTrue("Invalid size", outputMessage.getBodyAsBytes().length > 0);
BufferedImage result = ImageIO.read(new ByteArrayInputStream(outputMessage.getBodyAsBytes()));

View File

@ -26,9 +26,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.MockHttpInputMessage;
import org.springframework.http.MockHttpOutputMessage;
/**
* @author Arjen Poutsma
*/
/** @author Arjen Poutsma */
public class ByteArrayHttpMessageConverterTests {
private ByteArrayHttpMessageConverter converter;
@ -51,7 +49,7 @@ public class ByteArrayHttpMessageConverterTests {
public void write() throws IOException {
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
byte[] body = new byte[]{0x1, 0x2};
converter.write(body, outputMessage);
converter.write(body, null, outputMessage);
assertArrayEquals("Invalid result", body, outputMessage.getBodyAsBytes());
assertEquals("Invalid content-type", new MediaType("application", "octet-stream"),
outputMessage.getHeaders().getContentType());

View File

@ -30,9 +30,7 @@ import org.springframework.http.MockHttpOutputMessage;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* @author Arjen Poutsma
*/
/** @author Arjen Poutsma */
public class FormHttpMessageConverterTests {
private FormHttpMessageConverter converter;
@ -67,7 +65,7 @@ public class FormHttpMessageConverterTests {
body.add("name 2", "value 2+2");
body.add("name 3", null);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(body, outputMessage);
converter.write(body, null, outputMessage);
Charset iso88591 = Charset.forName("ISO-8859-1");
assertEquals("Invalid result", "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3",
outputMessage.getBodyAsString(iso88591));

View File

@ -18,7 +18,6 @@ package org.springframework.http.converter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import static org.junit.Assert.*;
import org.junit.Before;
@ -28,9 +27,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.MockHttpInputMessage;
import org.springframework.http.MockHttpOutputMessage;
/**
* @author Arjen Poutsma
*/
/** @author Arjen Poutsma */
public class StringHttpMessageConverterTests {
private StringHttpMessageConverter converter;
@ -55,7 +52,7 @@ public class StringHttpMessageConverterTests {
Charset iso88591 = Charset.forName("ISO-8859-1");
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
String body = "H\u00e9llo W\u00f6rld";
converter.write(body, outputMessage);
converter.write(body, null, outputMessage);
assertEquals("Invalid result", body, outputMessage.getBodyAsString(iso88591));
assertEquals("Invalid content-type", new MediaType("text", "plain", iso88591),
outputMessage.getHeaders().getContentType());
@ -67,13 +64,12 @@ public class StringHttpMessageConverterTests {
@Test
public void writeUTF8() throws IOException {
Charset utf8 = Charset.forName("UTF-8");
converter.setSupportedMediaTypes(Collections.singletonList(new MediaType("text", "plain", utf8)));
MediaType contentType = new MediaType("text", "plain", utf8);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
String body = "H\u00e9llo W\u00f6rld";
converter.write(body, outputMessage);
converter.write(body, contentType, outputMessage);
assertEquals("Invalid result", body, outputMessage.getBodyAsString(utf8));
assertEquals("Invalid content-type", new MediaType("text", "plain", utf8),
outputMessage.getHeaders().getContentType());
assertEquals("Invalid content-type", contentType, outputMessage.getHeaders().getContentType());
assertEquals("Invalid content-length", body.getBytes(utf8).length,
outputMessage.getHeaders().getContentLength());
assertFalse("Invalid accept-charset", outputMessage.getHeaders().getAcceptCharset().isEmpty());

View File

@ -39,7 +39,7 @@ public class MappingJacksonHttpMessageConverterTests {
public void setUp() {
converter = new MappingJacksonHttpMessageConverter<MyBean>();
}
@Test
public void supports() {
assertTrue(converter.supports(MyBean.class));
@ -68,10 +68,10 @@ public class MappingJacksonHttpMessageConverterTests {
"{\"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"));
HashMap<String,Object> result = converter.read(HashMap.class, inputMessage);
HashMap<String, Object> result = converter.read(HashMap.class, inputMessage);
assertEquals("Foo", result.get("string"));
assertEquals(42, result.get("number"));
assertEquals(42D, (Double)result.get("fraction"), 0D);
assertEquals(42D, (Double) result.get("fraction"), 0D);
List array = new ArrayList();
array.add("Foo");
array.add("Bar");
@ -90,7 +90,7 @@ public class MappingJacksonHttpMessageConverterTests {
body.setArray(new String[]{"Foo", "Bar"});
body.setBool(true);
body.setBytes(new byte[]{0x1, 0x2});
converter.write(body, outputMessage);
converter.write(body, null, outputMessage);
Charset utf8 = Charset.forName("UTF-8");
String result = outputMessage.getBodyAsString(utf8);
assertTrue(result.contains("\"string\":\"Foo\""));

View File

@ -68,7 +68,7 @@ public class MarshallingHttpMessageConverterTests {
marshaller.marshal(eq(body), isA(StreamResult.class));
replay(marshaller, unmarshaller);
converter.write(body, outputMessage);
converter.write(body, null, outputMessage);
assertEquals("Invalid content-type", new MediaType("application", "xml"),
outputMessage.getHeaders().getContentType());
verify(marshaller, unmarshaller);

View File

@ -94,7 +94,7 @@ public class SourceHttpMessageConverterTests {
SourceHttpMessageConverter<Source> converter = new SourceHttpMessageConverter<Source>();
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
converter.write(domSource, outputMessage);
converter.write(domSource, null, outputMessage);
assertXMLEqual("Invalid result", "<root>Hello World</root>",
outputMessage.getBodyAsString(Charset.forName("UTF-8")));
assertEquals("Invalid content-type", new MediaType("application", "xml"),

View File

@ -20,7 +20,6 @@ import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -36,9 +35,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
/** @author Arjen Poutsma */
@SuppressWarnings("unchecked")
@ -65,18 +62,7 @@ public class RestTemplateTests {
converter = createMock(HttpMessageConverter.class);
template = new RestTemplate(requestFactory);
template.setErrorHandler(errorHandler);
template.setMessageConverters(new HttpMessageConverter<?>[]{converter});
}
@Test
public void getSupportedMessageBodyConverters() {
ByteArrayHttpMessageConverter byteArrayConverter = new ByteArrayHttpMessageConverter();
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
template.setMessageConverters(new HttpMessageConverter<?>[]{byteArrayConverter, stringConverter});
List<HttpMessageConverter<String>> result = template.getSupportedMessageConverters(String.class);
assertEquals("Invalid amount of String converters", 1, result.size());
assertEquals("Invalid String converters", stringConverter, result.get(0));
template.setMessageConverters(Collections.<HttpMessageConverter<?>>singletonList(converter));
}
@Test
@ -136,9 +122,9 @@ public class RestTemplateTests {
@Test
public void getForObject() throws Exception {
expect(converter.supports(String.class)).andReturn(true).times(2);
expect(converter.canRead(String.class, null)).andReturn(true);
MediaType textPlain = new MediaType("text", "plain");
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain)).times(2);
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain));
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET)).andReturn(request);
HttpHeaders requestHeaders = new HttpHeaders();
expect(request.getHeaders()).andReturn(requestHeaders);
@ -147,6 +133,7 @@ public class RestTemplateTests {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(textPlain);
expect(response.getHeaders()).andReturn(responseHeaders);
expect(converter.canRead(String.class, textPlain)).andReturn(true);
String expected = "Hello World";
expect(converter.read(String.class, response)).andReturn(expected);
response.close();
@ -160,28 +147,11 @@ public class RestTemplateTests {
verifyMocks();
}
@Test
public void getForObjectUnsupportedClass() throws Exception {
expect(converter.supports(String.class)).andReturn(false);
replayMocks();
try {
template.getForObject("http://example.com/{p}", String.class, "resource");
fail("IllegalArgumentException expected");
}
catch (IllegalArgumentException ex) {
// expected
}
verifyMocks();
}
@Test
public void getUnsupportedMediaType() throws Exception {
expect(converter.supports(String.class)).andReturn(true).times(2);
expect(converter.canRead(String.class, null)).andReturn(true);
MediaType supportedMediaType = new MediaType("foo", "bar");
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(supportedMediaType)).times(2);
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(supportedMediaType));
expect(requestFactory.createRequest(new URI("http://example.com/resource"), HttpMethod.GET)).andReturn(request);
HttpHeaders requestHeaders = new HttpHeaders();
expect(request.getHeaders()).andReturn(requestHeaders);
@ -191,6 +161,7 @@ public class RestTemplateTests {
MediaType contentType = new MediaType("bar", "baz");
responseHeaders.setContentType(contentType);
expect(response.getHeaders()).andReturn(responseHeaders);
expect(converter.canRead(String.class, contentType)).andReturn(false);
response.close();
replayMocks();
@ -224,10 +195,10 @@ public class RestTemplateTests {
@Test
public void postForLocation() throws Exception {
expect(converter.supports(String.class)).andReturn(true).times(2);
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request);
String helloWorld = "Hello World";
converter.write(helloWorld, request);
expect(converter.canWrite(String.class, null)).andReturn(true);
converter.write(helloWorld, null, request);
expect(request.execute()).andReturn(response);
expect(errorHandler.hasError(response)).andReturn(false);
HttpHeaders responseHeaders = new HttpHeaders();
@ -246,10 +217,10 @@ public class RestTemplateTests {
@Test
public void postForLocationNoLocation() throws Exception {
expect(converter.supports(String.class)).andReturn(true).times(2);
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request);
String helloWorld = "Hello World";
converter.write(helloWorld, request);
expect(converter.canWrite(String.class, null)).andReturn(true);
converter.write(helloWorld, null, request);
expect(request.execute()).andReturn(response);
expect(errorHandler.hasError(response)).andReturn(false);
HttpHeaders responseHeaders = new HttpHeaders();
@ -284,21 +255,22 @@ public class RestTemplateTests {
@Test
public void postForObject() throws Exception {
expect(converter.supports(String.class)).andReturn(true).times(2);
expect(converter.supports(Integer.class)).andReturn(true).times(2);
MediaType textPlain = new MediaType("text", "plain");
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain)).times(2);
expect(converter.canRead(Integer.class, null)).andReturn(true);
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain));
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(this.request);
HttpHeaders requestHeaders = new HttpHeaders();
expect(this.request.getHeaders()).andReturn(requestHeaders);
String request = "Hello World";
converter.write(request, this.request);
expect(converter.canWrite(String.class, null)).andReturn(true);
converter.write(request, null, this.request);
expect(this.request.execute()).andReturn(response);
expect(errorHandler.hasError(response)).andReturn(false);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(textPlain);
expect(response.getHeaders()).andReturn(responseHeaders);
Integer expected = 42;
expect(converter.canRead(Integer.class, textPlain)).andReturn(true);
expect(converter.read(Integer.class, response)).andReturn(expected);
response.close();
@ -313,9 +285,9 @@ public class RestTemplateTests {
@Test
public void postForObjectNull() throws Exception {
expect(converter.supports(Integer.class)).andReturn(true).times(2);
MediaType textPlain = new MediaType("text", "plain");
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain)).times(2);
expect(converter.canRead(Integer.class, null)).andReturn(true);
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(textPlain));
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).andReturn(request);
HttpHeaders requestHeaders = new HttpHeaders();
expect(request.getHeaders()).andReturn(requestHeaders).times(2);
@ -324,6 +296,7 @@ public class RestTemplateTests {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(textPlain);
expect(response.getHeaders()).andReturn(responseHeaders);
expect(converter.canRead(Integer.class, textPlain)).andReturn(true);
expect(converter.read(Integer.class, response)).andReturn(null);
response.close();
@ -336,10 +309,10 @@ public class RestTemplateTests {
@Test
public void put() throws Exception {
expect(converter.supports(String.class)).andReturn(true).times(2);
expect(converter.canWrite(String.class, null)).andReturn(true);
expect(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.PUT)).andReturn(request);
String helloWorld = "Hello World";
converter.write(helloWorld, request);
converter.write(helloWorld, null, request);
expect(request.execute()).andReturn(response);
expect(errorHandler.hasError(response)).andReturn(false);
response.close();
@ -402,7 +375,7 @@ public class RestTemplateTests {
@Test
public void ioException() throws Exception {
expect(converter.supports(String.class)).andReturn(true).times(2);
expect(converter.canRead(String.class, null)).andReturn(true);
MediaType mediaType = new MediaType("foo", "bar");
expect(converter.getSupportedMediaTypes()).andReturn(Collections.singletonList(mediaType));
expect(requestFactory.createRequest(new URI("http://example.com/resource"), HttpMethod.GET)).andReturn(request);