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}