View Javadoc
1   package pl.matsuo.core.web.mvc;
2   
3   import com.google.gson.Gson;
4   import com.google.gson.reflect.TypeToken;
5   import org.apache.commons.io.IOUtils;
6   import org.springframework.beans.factory.annotation.Autowired;
7   import org.springframework.core.Conventions;
8   import org.springframework.core.MethodParameter;
9   import org.springframework.core.annotation.AnnotationUtils;
10  import org.springframework.stereotype.Component;
11  import org.springframework.validation.BindingResult;
12  import org.springframework.validation.Errors;
13  import org.springframework.web.bind.MethodArgumentNotValidException;
14  import org.springframework.web.bind.WebDataBinder;
15  import org.springframework.web.bind.annotation.RequestBody;
16  import org.springframework.web.bind.support.WebDataBinderFactory;
17  import org.springframework.web.context.request.NativeWebRequest;
18  import org.springframework.web.method.support.HandlerMethodArgumentResolver;
19  import org.springframework.web.method.support.ModelAndViewContainer;
20  import pl.matsuo.core.IRequestParams;
21  import pl.matsuo.core.service.facade.FacadeBuilder;
22  import pl.matsuo.core.service.parameterprovider.IParameterProvider;
23  import pl.matsuo.core.service.parameterprovider.MapParameterProvider;
24  
25  import javax.servlet.http.HttpServletRequest;
26  import java.lang.annotation.Annotation;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import static java.util.Arrays.*;
33  
34  
35  /**
36   * Mapping request body to IRequestParams subinterfaces, allowing to use them in controller methods:
37   *
38   * <pre>
39       \@RequestMapping(value = "updateOwnPassword", method = PUT, consumes = { APPLICATION_JSON_VALUE })
40       \@ResponseStatus(NO_CONTENT)
41       public void updateOwnPassword(@RequestBody IChangePasswordParams changePasswordParams) {
42   * </pre>
43   *
44   * If RequestBody annotation is present, parameters instance will be created on request's input stream. If not,
45   * it will be created basing on request's params.
46   *
47   * <p>
48   * Created by tunguski on 23.11.13.
49   * </p>
50   */
51  @Component
52  public class FacadeBuilderHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
53  
54  
55    @Autowired
56    FacadeBuilder facadeBuilder;
57    protected Gson gson = new Gson();
58  
59  
60    @Override
61    public boolean supportsParameter(MethodParameter parameter) {
62      return IRequestParams.class.isAssignableFrom(parameter.getParameterType())
63          || IParameterProvider.class.equals(parameter.getParameterType());
64    }
65  
66  
67    @Override
68    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
69                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
70      boolean returnFacade = IRequestParams.class.isAssignableFrom(parameter.getParameterType());
71      Object facade;
72      if (parameter.hasParameterAnnotation(RequestBody.class)) {
73        String body = IOUtils.toString(webRequest.getNativeRequest(HttpServletRequest.class).getInputStream());
74        if (returnFacade) {
75          facade = facadeBuilder.createFacade(
76              gson.fromJson(body, new TypeToken<Map<String, Object>>(){}.getType()),
77              parameter.getParameterType());
78        } else {
79          facade = facadeBuilder.createParameterProvider(
80              gson.fromJson(body, new TypeToken<Map<String, Object>>(){}.getType()));
81        }
82      } else {
83        Map<String, String[]> parameterMap = webRequest.getParameterMap();
84        Map<String, List<String>> params = new HashMap<>();
85        for (String key : parameterMap.keySet()) {
86          String[] values = parameterMap.get(key);
87          params.put(key, new ArrayList<>(asList(values)));
88        }
89  
90        IParameterProvider<?> parameterProvider = new MapParameterProvider(params) {
91          @Override
92          public Object internalGet(String key, Class<?> expectedClass) {
93            if (expectedClass.equals(List.class)) {
94              return super.internalGet(key, expectedClass);
95            } else {
96              Object list = super.internalGet(key, Object.class);
97              if (list == null) {
98                return null;
99              } else if (List.class.isAssignableFrom(list.getClass())) {
100               return ((List) list).get(0);
101             } else {
102               return list;
103             }
104           }
105         }
106       };
107 
108       if (returnFacade) {
109         facade = facadeBuilder.createFacade(parameterProvider, parameter.getParameterType());
110       } else {
111         facade = parameterProvider;
112       }
113     }
114 
115     String name = Conventions.getVariableNameForParameter(parameter);
116     WebDataBinder binder = binderFactory.createBinder(webRequest, facade, name);
117     validate(binder, parameter);
118 
119     mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
120 
121     return facade;
122   }
123 
124 
125   private void validate(WebDataBinder binder, MethodParameter parameter) throws Exception {
126 
127     Annotation[] annotations = parameter.getParameterAnnotations();
128     for (Annotation annot : annotations) {
129       if (annot.annotationType().getSimpleName().startsWith("Valid")) {
130         Object hints = AnnotationUtils.getValue(annot);
131         binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
132         BindingResult bindingResult = binder.getBindingResult();
133         if (bindingResult.hasErrors()) {
134           if (isBindExceptionRequired(binder, parameter)) {
135             throw new MethodArgumentNotValidException(parameter, bindingResult);
136           }
137         }
138         break;
139       }
140     }
141   }
142 
143 
144   /**
145    * Whether to raise a {@link MethodArgumentNotValidException} on validation errors.
146    * @param binder the data binder used to perform data binding
147    * @param parameter the method argument
148    * @return {@code true} if the next method argument is not of type {@link org.springframework.validation.Errors}.
149    */
150   private boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
151     int i = parameter.getParameterIndex();
152     Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
153     boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
154 
155     return !hasBindingResult;
156   }
157 }
158