Spring Custom Annotation Solver MulitpartFile Resolves to Null - spring

I have a Spring custom annotation which I am using instead of #requestParam
public Response uploadImages(#myResolver(value = "imageFile", required = true) final MultipartFile multiPartFile) {...}
However, the mulitpart file parameter is resolved to null. So I put a breakpoint in my HandlerMethodArgumentResolver.resolveArgument to see if the argument was being resolved, but the breakpoint is never reached. But I know my custom annotation works for other params such as String, long e.t.c.
When I try using #requestParam instead it works fine, and the multipart file is resolved.
public Response uploadImages(#requestParam(value = "imageFile", required = true) final MultipartFile multiPartFile) {...}
Does anyone know why mulitpart file is being resolved to null using my custom parameter resolver and not using resolveArgument and how I could solve this please?
Thanks!
EDIT
The config for adding argument resolvers
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
argumentResolvers.add(new MyAnnotationResolver());
}
My annotation definition
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Component
public #interface MyResolver{
String value() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
String errorCode() default "40000";

Related

Spring #Value not working in Spring Boot 2.5.5, getting null values

I am trying to inject some property values into variables by means of Spring #Value annotation but I get null values. I tried different configurations and triks but it doesn't work. Think is that before today everythink was working properly. I do not know what I changed in order to get things broken.
Here is my java class:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
#Value("${studioghibli.basepath}")
private static String BASE_PATH;
#Value("${studioghibli.path}")
private static String PATH;
#Value("${studioghibli.protocol:http}")
private static String PROTOCOL;
#Value("${studioghibli.host}")
private static String HOST;
private static String BASE_URI = PROTOCOL.concat("://").concat(HOST).concat(BASE_PATH).concat(PATH);
#Autowired
StudioGhibliRestConnector connector;
public List<StudioGhibliFilmDTO> findAllFilms() throws SipadContenziosoInternalException {
var response = connector.doGet(BASE_URI, null, null);
if (!response.getStatusCode().is2xxSuccessful() || !response.hasBody()) {
throw new SipadContenziosoInternalException(Errore.INTERNAL_REST_ERROR, "FindAll(), microservizio ".concat(BASE_URI), null);
}
return (List<StudioGhibliFilmDTO>) response.getBody();
}
}
As you can see, the class is annotated with #Component, that because I will need to use it as #Service layer in order to make a rest call in my business logic.
The class is also annotaded with conditional on property...
Here is a screenshot of the debug window at startup:
Since the PROTOCOL value is null, i get a null pointer exception immediately at start up.
Here is part of the application-dev.properties file:
studioghibli.get
studioghibli.protocol=https
studioghibli.host=ghibliapi.herokuapp.com
studioghibli.basepath=/
studioghibli.path=/films
First of all, #Value annotation does not work with static fields.
Secondly, fields with #Value annotation is processed when the instance of the class (a bean) is created by Spring, but static fields exist for a class (for any instance), so when the compiler is trying to define your static BASE_URI field other fields are not defined yet, so you get the NPE on startup.
So you might need a refactoring, try to inject values with the constructor like this:
#Component
#ConditionalOnProperty(prefix = "studioghibli", name = "get")
public class StudioGhibliRestService {
private final StudioGhibliRestConnector connector;
private final String baseUri;
public StudioGhibliRestService(StudioGhibliRestConnector connector,
#Value("${studioghibli.basepath}") String basePath,
#Value("${studioghibli.path}") String path,
#Value("${studioghibli.protocol:http}") String protocol,
#Value("${studioghibli.host}") String host) {
this.connector = connector;
this.baseUri = protocol.concat("://").concat(host).concat(basePath).concat(path);
}
// other code
}
Thanks, It works for me, I have to add some codes to my project. Then I check the spring core document in "#Value" section. Besides
When configuring a PropertySourcesPlaceholderConfigurer using
JavaConfig, the #Bean method must be static.
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}

FormUrlEncoded POST request, I need to convert snake case values into camelCase with SpringBoot and Jackson

I am integrating with a third-party's vendor API.
I have a SpringBoot and Jackson setup
They are sending me a POST request that is of type formUrlEncoded and with the params in snake_case
(over 10 params in total and no body)
e.g.
POST www.example.com?player_id=somePlayerId&product_id=someProductId&total_amount=totalAmount...
There are many out of the box helpers for JSON but I cannot find any for formUrlEncoded (I hope I am missing something obvious).
I have tried #ModelAttribute and #RequestParam but had no luck.
I am trying to avoid the #RequestParam MultiValueMap<String, String> params + custom mapper option
#RequestParam is the simplest way which allows you to define the exact name of the query parameter something like:
#PostMapping
public String foo(#RequestParam("player_id") String playerId){
}
If you want to bind all the query parameters to an object , you have to use #ModelAttribute. It is based on the DataBinder and is nothing to do with Jackson. By default it only supports binding the query parameter to an object which fields have the same name as the query parameter. So you can consider to bind the query paramater to the following object :
public class Request {
private String player_id;
private String product_id;
private Long total_amount;
}
If you really want to bind to the object that follow traditional java naming convention (i.e lower camel case) from the query parameter that has snake case values , you have to cusomtize WebDataBinder.
The idea is to override its addBindValues() and check if the query parameter name is in snake case format , convert it the lower camel case format and also add it as the bind values for the request. Something like :
public class MyServletRequestDataBinder extends ExtendedServletRequestDataBinder {
private static Converter<String, String> snakeCaseToLowerCamelConverter = CaseFormat.LOWER_UNDERSCORE
.converterTo(CaseFormat.LOWER_CAMEL);
public MyServletRequestDataBinder(Object target) {
super(target);
}
public MyServletRequestDataBinder(Object target, String objectName) {
super(target, objectName);
}
#Override
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
super.addBindValues(mpvs, request);
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames != null && paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
if(paramName.contains("_")) {
String[] values = request.getParameterValues(paramName);
if (values == null || values.length == 0) {
// Do nothing, no values found at all.
} else if (values.length > 1) {
mpvs.addPropertyValue(snakeCaseToLowerCamelConverter.convert(paramName), values);
} else {
mpvs.addPropertyValue(snakeCaseToLowerCamelConverter.convert(paramName), values[0]);
}
}
}
}
}
P.S I am using Guava for helping me to convert snake case to lowerCamelCase.
But in order to use the customized WebDataBinder , you have to in turn customize WebDataBinderFactory and RequestMappingHandlerAdapter because :
customize WebDataBinderFactory in order to create the customised WebDataBinder
customize RequestMappingHandlerAdapter in order to create the WebDataBinderFactory
Something like:
public class MyServletRequestDataBinderFactory extends ServletRequestDataBinderFactory {
public MyServletRequestDataBinderFactory(List<InvocableHandlerMethod> binderMethods,
WebBindingInitializer initializer) {
super(binderMethods, initializer);
}
#Override
protected ServletRequestDataBinder createBinderInstance(Object target, String objectName,
NativeWebRequest request) throws Exception {
return new MyServletRequestDataBinder(target, objectName);
}
}
and
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
#Override
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
throws Exception {
return new MyServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
}
And finally register to use the customised RequestMappingHandlerAdapter in your configuration :
#Configuration
public class Config extends DelegatingWebMvcConfiguration {
#Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
return new MyRequestMappingHandlerAdapter();
}
}
I don't think you are missing anything. Looking at the RequestParamMethodArgumentResolver#resolveName source I do no see a way to customize how a request parameter is matched. So it looks either you have to implement your own resolver or just annotate each parameter with #RequestParam and provide the name, e.g. #RequestParam("product_id") String productId
EDIT:
As for ModelAttribute, ModelAttributeMethodProcessor uses WebDataBinder. Again you can customize it with your custom DataBinder but I didn't found any that out of the box supports aliases as Jackson does.

Unable to read property from application.yml using #Value in Spring boot

My code is unable to read a property value from application.yml in 1 class whereas it is able to read in another class.
Please see my class below:
#Component
#EnableConfigurationProperties
public class DCCIAccessTokenProvider extends ClientCredentialsAccessTokenProvider{
#Value("${authCode}")
private String authorizationCode="";
#Override
protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource,
MultiValueMap<String, String> form, HttpHeaders headers) throws OAuth2AccessDeniedException {
headers.add("Authorization", "Basic "+authorizationCode);
return super.retrieveToken(request, resource, form, headers);
}
}
Here, in authorizationCode, I am getting blank while debugging.
I have another class, in which value is getting read properly. Please see below:
#Component
#EnableConfigurationProperties
public class SearchBookingProcessor {
#Autowired
private OAuth2RestTemplate dcciRestTemplate;
public OAuth2RestTemplate getDcciRestTemplate() {
return dcciRestTemplate;
}
public void setDcciRestTemplate(OAuth2RestTemplate dcciRestTemplate) {
this.dcciRestTemplate = dcciRestTemplate;
}
#Value("${api.dcci.searchBooking}")
private String DCCI_API="";
#Value("${jipcc}")
private String JIPCC="";
}
Here the values are getting read properly.
Please let me know what can be the issue here. Thanks in advance!!
Is there any specific reason for assigning an empty string to the property variable? If that is to set the default value if the property is not available in the file, please try the below line.
#Value("${authCode:}")
private String authorizationCode;
Note the ":" after authCode
Let me know how it goes.
Thanks for all the help. I resolved the problem with the help of my colleague. I was using new object of DCCIAccessTokenProvider, instead of getting it from ApplicationContext and hence it was not able to read the value.

Spring Validation Errors for RequestParam

I want to pass org.springframework.validation.Errors to CodeValidator class.
But, since I am not using RequestBody/RequestPart/ModelAttribute, I cannot put Errors in method param after variable.
I use #RequestParam for code variable, and I want to validate that using CodeValidator class that implement org.springframework.validation.Validator.
Here is my code
#RequestMapping(value = "/check-code", method = RequestMethod.POST)
public ResponseEntity<Object> checkCode(#RequestParam("code") String code, Errors errors) {
codeValidator.validate(code, errors);
if(errors.hasErrors()) {
return ResponseEntity.badRequest().body("Errors");
}
return ResponseEntity.ok("");
}
and here error result for my code:
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 org.springframework.http.ResponseEntity com.example.myapp.controller.CodeController.checkCode(java.lang.String,org.springframework.validation.BindingResult)
what should I do to be able using CodeValidator with #RequestParam?
Updated:
Code for CodeValidator
#Service
public class CodeValidator implements Validator {
#Override
public void validate(Object target, Errors errors) {
String code = ((String) target);
if(code == null || code.isEmpty()) {
errors.rejectValue("code", "", "Please fill in Code.");
}
}
}
Did you create an annotation with your validator?
Otherwise take a look at a small example/tutorial for custom validating with spring: https://www.baeldung.com/spring-mvc-custom-validator
(edit) if you are using spring boot you might need add a MethodValidationPostProcessor bean to your spring config to enable custom valdation for the #requesParam

Using MVC type conversion for path variable and returning 404 on null parameter

My controller. Note the custom #Exists annotation:
#RestController
public class ClientApiController {
#RequestMapping(path = "/{client}/someaction", method = RequestMethod.GET)
String handleRequest(#Exists Client client) {
// ...
}
}
The Exists annotation:
/**
* Indicates that a controller request mapping method parametet should not be
* null. This is meant to be used on model types to indicate a required entity.
*/
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Exists {}
The converter which converts the String from the path variable into a Client instance:
#Component
public class StringToClient implements Converter<String, Client> {
#Autowired
private ClientDAO clientDAO;
#Override
public Client convert(String source) {
return clientDAO.getClientById(source);
}
}
The ResourceNotFoundException exception used to trigger a 404
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}
My controller method receives the converted Client as desired. If the client id used in the URL matches a client, everything works fine. If the id doesn't match, the client parameter is null empty (uses default constructor) in the handle() controller method.
What I can't get to work now is declarative checking that the Client is not null (i.e. that the id refers to an existing client). If it's null, a ResourceNotFoundException should be thrown. Checking whether the argument is null in the method body and throwing my custom ResourceNotFoundException is easy to do, but repetitive (like this one does). Also, this declarative approach should work for all model classes implementing the interface ModelWithId so it can be used for multiple model types.
I've searched the Spring documentation and I haven't found how to achieve this. I need to insert some processing somewhere after type conversion and before the controller's handleRequest method.
I'm using Spring Boot 1.3.3
After type conversion and before the controller's method there is a validation. You can implement custom validator and raise exception in it. Add new validator to DataBinder, and mark method's parameter as #Validated:
#RestController
public class ClientApiController {
#InitBinder
public void initBinder(DataBinder binder){
binder.addValidators(new Validator() {
#Override
public boolean supports(Class<?> aClass) {
return aClass==Client.class;
}
#Override
public void validate(Object o, Errors errors) {
Client client = (Client)o;
if(client.getId()==null) throw new ResourceNotFoundException();
}
});
}
#RequestMapping(path = "/{client}/someaction", method = RequestMethod.GET)
String handleRequest(#Validated #Exists Client client) {
// ...
}
#RequestMapping(path = "/{client}/anotheraction", method = RequestMethod.GET)
String handleAnotherRequest(#Validated #Exists Client client) {
// ...
}
}
Of course, you can declare validator as separate class, and use it repeatedly in other controllers. Actually, you can raise exception right in your converter, but there is possibility, that you'll need the conversion without exception in other places of your application.

Resources