2
votes

I would like to create own class that will transform HTTP request and initializes object from this HTTP request in my Spring MVC application. I can create object by defining parameters in method but I need to do mapping in my own way and do it manually.

How can I do it with my own implementation that will pass to Spring and it will use it seamlessly?

Update1

Solution that kindly provided Bohuslav Burghardt doesn't work:

HTTP Status 500 - Request processing failed; nested exception is java.lang.IllegalStateException: An Errors/BindingResult argument is expected to be declared immediately after the model attribute, the @RequestBody or the @RequestPart arguments to which they apply: public java.lang.String cz.deriva.derivis.api.oauth2.provider.controllers.OAuthController.authorize(api.oauth2.provider.domain.AuthorizationRequest,org.springframework.ui.Model,org.springframework.validation.BindingResult,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)

Maybe I should mention that I use own validator:

public class RequestValidator {

    public boolean supports(Class clazz) {
        return AuthorizationRequest.class.equals(clazz);
    }

    public void validate(Object obj, Errors e) {
        AuthorizationRequest request = (AuthorizationRequest) obj;
        if ("foobar".equals(request.getClientId())) {
            e.reject("clientId", "nomatch");
        }
    }

}

and declaration of my method in controller (please not there is needed a validation - @Valid):

@RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
    public String authorize(
            @Valid AuthorizationRequest authorizationRequest,
            BindingResult result
    ) {

}

I have two configurations classes in my application.

@Configuration
@EnableAutoConfiguration
@EnableWebMvc
@PropertySource("classpath:/jdbc.properties")
public class ApplicationConfig {
}

and

@Configuration
@EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new AuthorizationRequestArgumentResolver());
    }

}

What is wrong?

Update 2

The problem is with param BindingResult result, when I remove it it works. But I need the result to process it when some errors occur.

1
The problem seems to be caused by the fact that you can't use HandlerMethodArgumentResolver with @Valid annotation. See this SO post for more details. Unforunately I've never personally used it in this scenario. Maybe some information from that post will help. I will try to look into it and update my answer with a solution, if I find one that works. - Bohuslav Burghardt
Yes, probably, I have no idea why I cannot use HandlerMethodArgumentResolver and @Valid at the same time. Diky. - Artegon
What is a authRequest in your code? I assume to add this code to my HandlerMethodArgumentResolver implementation? - Artegon
Removed previous comment. See updated answer for workaround. The authRequest is supposed to be your authorization request (updated it from foo to reflect the changes in the question). Let me know if it works - Bohuslav Burghardt

1 Answers

1
votes

If I understand your requirements correctly, you could implement custom HandlerMethodArgumentResolver for that purpose. See example below for implementation details:


Model object

public class AuthorizationRequestHolder {
    @Valid
    private AuthorizationRequest authorizationRequest;
    private BindingResult bindingResult;
    // Constructors, accessors omitted
}

Resolver

public class AuthorizationRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return AuthorizationRequestHolder.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter,
                                  ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        // Map the authorization request
        AuthorizationRequest authRequest = mapFromServletRequest(request);
        AuthorizationRequestHolder authRequestHolder = new AuthorizationRequestHolder(authRequest);
        // Validate the request
        if (parameter.hasParameterAnnotation(Valid.class)) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, authRequestHolder, parameter.getParameterName());
            binder.validate();
            authRequestHolder.setBindingResult(binder.getBindingResult());
        }
        return authRequestHolder;
    }
}

Configuration

@Configuration
@EnableWebMvc
public class WebappConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new AuthorizationRequestMethodArgumentResolver());
    }
}

Usage

@RequestMapping("/auth")
public void doSomething(@Valid AuthRequestHolder authRequestHolder) {
    if (authRequestHolder.getBindingResult().hasErrors()) {
        // Process errors
    }
    AuthorizationRequest authRequest = authRequestHolder.getAuthRequest();
    // Do something with the authorization request
}

Edit: Updated answer with workaround to non-supported usage of @Valid with HandlerMethodArgumentResolver parameters.