001/* 002 * Copyright 2002-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.messaging.handler.annotation.support; 018 019import java.lang.annotation.Annotation; 020 021import org.springframework.core.MethodParameter; 022import org.springframework.core.annotation.AnnotationUtils; 023import org.springframework.messaging.Message; 024import org.springframework.messaging.converter.MessageConversionException; 025import org.springframework.messaging.converter.MessageConverter; 026import org.springframework.messaging.converter.SmartMessageConverter; 027import org.springframework.messaging.handler.annotation.Payload; 028import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; 029import org.springframework.util.Assert; 030import org.springframework.util.ClassUtils; 031import org.springframework.util.ObjectUtils; 032import org.springframework.util.StringUtils; 033import org.springframework.validation.BeanPropertyBindingResult; 034import org.springframework.validation.BindingResult; 035import org.springframework.validation.ObjectError; 036import org.springframework.validation.SmartValidator; 037import org.springframework.validation.Validator; 038import org.springframework.validation.annotation.Validated; 039 040/** 041 * A resolver to extract and convert the payload of a message using a 042 * {@link MessageConverter}. It also validates the payload using a 043 * {@link Validator} if the argument is annotated with a Validation annotation. 044 * 045 * <p>This {@link HandlerMethodArgumentResolver} should be ordered last as it 046 * supports all types and does not require the {@link Payload} annotation. 047 * 048 * @author Rossen Stoyanchev 049 * @author Brian Clozel 050 * @author Stephane Nicoll 051 * @since 4.0 052 */ 053public class PayloadArgumentResolver implements HandlerMethodArgumentResolver { 054 055 private final MessageConverter converter; 056 057 private final Validator validator; 058 059 private final boolean useDefaultResolution; 060 061 062 /** 063 * Create a new {@code PayloadArgumentResolver} with the given 064 * {@link MessageConverter}. 065 * @param messageConverter the MessageConverter to use (required) 066 * @since 4.0.9 067 */ 068 public PayloadArgumentResolver(MessageConverter messageConverter) { 069 this(messageConverter, null); 070 } 071 072 /** 073 * Create a new {@code PayloadArgumentResolver} with the given 074 * {@link MessageConverter} and {@link Validator}. 075 * @param messageConverter the MessageConverter to use (required) 076 * @param validator the Validator to use (optional) 077 */ 078 public PayloadArgumentResolver(MessageConverter messageConverter, Validator validator) { 079 this(messageConverter, validator, true); 080 } 081 082 /** 083 * Create a new {@code PayloadArgumentResolver} with the given 084 * {@link MessageConverter} and {@link Validator}. 085 * @param messageConverter the MessageConverter to use (required) 086 * @param validator the Validator to use (optional) 087 * @param useDefaultResolution if "true" (the default) this resolver supports 088 * all parameters; if "false" then only arguments with the {@code @Payload} 089 * annotation are supported. 090 */ 091 public PayloadArgumentResolver(MessageConverter messageConverter, Validator validator, 092 boolean useDefaultResolution) { 093 094 Assert.notNull(messageConverter, "MessageConverter must not be null"); 095 this.converter = messageConverter; 096 this.validator = validator; 097 this.useDefaultResolution = useDefaultResolution; 098 } 099 100 101 @Override 102 public boolean supportsParameter(MethodParameter parameter) { 103 return (parameter.hasParameterAnnotation(Payload.class) || this.useDefaultResolution); 104 } 105 106 @Override 107 public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception { 108 Payload ann = parameter.getParameterAnnotation(Payload.class); 109 if (ann != null && StringUtils.hasText(ann.expression())) { 110 throw new IllegalStateException("@Payload SpEL expressions not supported by this resolver"); 111 } 112 113 Object payload = message.getPayload(); 114 if (isEmptyPayload(payload)) { 115 if (ann == null || ann.required()) { 116 String paramName = getParameterName(parameter); 117 BindingResult bindingResult = new BeanPropertyBindingResult(payload, paramName); 118 bindingResult.addError(new ObjectError(paramName, "Payload value must not be empty")); 119 throw new MethodArgumentNotValidException(message, parameter, bindingResult); 120 } 121 else { 122 return null; 123 } 124 } 125 126 Class<?> targetClass = parameter.getParameterType(); 127 Class<?> payloadClass = payload.getClass(); 128 if (ClassUtils.isAssignable(targetClass, payloadClass)) { 129 validate(message, parameter, payload); 130 return payload; 131 } 132 else { 133 if (this.converter instanceof SmartMessageConverter) { 134 SmartMessageConverter smartConverter = (SmartMessageConverter) this.converter; 135 payload = smartConverter.fromMessage(message, targetClass, parameter); 136 } 137 else { 138 payload = this.converter.fromMessage(message, targetClass); 139 } 140 if (payload == null) { 141 throw new MessageConversionException(message, "Cannot convert from [" + 142 payloadClass.getName() + "] to [" + targetClass.getName() + "] for " + message); 143 } 144 validate(message, parameter, payload); 145 return payload; 146 } 147 } 148 149 private String getParameterName(MethodParameter param) { 150 String paramName = param.getParameterName(); 151 return (paramName != null ? paramName : "Arg " + param.getParameterIndex()); 152 } 153 154 /** 155 * Specify if the given {@code payload} is empty. 156 * @param payload the payload to check (can be {@code null}) 157 */ 158 protected boolean isEmptyPayload(Object payload) { 159 if (payload == null) { 160 return true; 161 } 162 else if (payload instanceof byte[]) { 163 return ((byte[]) payload).length == 0; 164 } 165 else if (payload instanceof String) { 166 return !StringUtils.hasText((String) payload); 167 } 168 else { 169 return false; 170 } 171 } 172 173 /** 174 * Validate the payload if applicable. 175 * <p>The default implementation checks for {@code @javax.validation.Valid}, 176 * Spring's {@link org.springframework.validation.annotation.Validated}, 177 * and custom annotations whose name starts with "Valid". 178 * @param message the currently processed message 179 * @param parameter the method parameter 180 * @param target the target payload object 181 * @throws MethodArgumentNotValidException in case of binding errors 182 */ 183 protected void validate(Message<?> message, MethodParameter parameter, Object target) { 184 if (this.validator == null) { 185 return; 186 } 187 for (Annotation ann : parameter.getParameterAnnotations()) { 188 Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); 189 if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { 190 Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); 191 Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); 192 BeanPropertyBindingResult bindingResult = 193 new BeanPropertyBindingResult(target, getParameterName(parameter)); 194 if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator) { 195 ((SmartValidator) this.validator).validate(target, bindingResult, validationHints); 196 } 197 else { 198 this.validator.validate(target, bindingResult); 199 } 200 if (bindingResult.hasErrors()) { 201 throw new MethodArgumentNotValidException(message, parameter, bindingResult); 202 } 203 break; 204 } 205 } 206 } 207 208}