How to get Spring Boot + Data Rest to resolve validation messages based on Accept-Language? - spring

I've got an API application based on Spring Boot (1.2.0.RC2) and Spring Data Rest, using Hibernate bean validation (out-of-the-box).
In order to expose invalid input (400 Bad Request) to the API clients I've created an exception handler class annotated with #ControllerAdvice which has the following method:
#ExceptionHandler
#ResponseBody ResponseEntity handleViolation(ConstraintViolationException exception) {
ErrorRepresentation.Builder builder = new ErrorRepresentation.Builder()
.withViolations(
exception.getConstraintViolations().stream()
.map(v -> {
ErrorRepresentation.Builder violationBuilder = new ErrorRepresentation.Builder();
violationBuilder.withDetail(v.getMessage());
violationBuilder.withInternalRef(v.getPropertyPath().iterator().next().getName());
return violationBuilder;
})
.map(ErrorRepresentation.Builder::build)
.collect(Collectors.toList()));
builder.withStatus(HttpStatus.BAD_REQUEST.value());
ErrorRepresentation error = builder.build();
return createResponseEntity(error);
}
Here I'm mapping javax.validation.ConstraintViolation into my own custom error representation class, which is then serialized to JSON. So far so good!
i18n is working, but only based on the default locale of the server environment.
I was hoping Spring had bootstrapped message interpolation automatically, such that the Accept-Language would be picked up and the Locale would be changed per request before message interpolation occurred.
Any hints on how to put this together?

Related

SessionLocaleResolver equivalent in Spring WebFlux - resolve locale through session data

If we need to resolve the locale value from session data, how can we implement this in a reactive way in Spring WebFlux?
For non-reactive web application, SessionLocaleResolver is available to resolve the locale from session data.
For Spring WebFlux, the way to intercept the locale resolver is using LocaleContextResolver. The default implementation provided in Spring Boot WebFlux starter is AcceptHeaderLocaleContextResolver. The problem with this interface is that it only provides sync method resolveLocaleContext(ServerWebExchange). If I need to resolve the locale from request query parameters or the Accept-Language headers, it works perfectly, because ServerWebExchange provides direct methods to access the request object, which in turn provides methods to access parameters and headers directly.
override fun resolveLocaleContext(exchange: ServerWebExchange): LocaleContext {
val langParams = exchange.request.queryParams["lang"]
var targetLocale: Locale? = null
if (langParams != null && langParams.isNotEmpty()) {
for (lang in langParams) {
val locale = Locale.forLanguageTag(lang)
if (locale != null) {
targetLocale = locale
break
}
}
}
return SimpleLocaleContext(targetLocale ?: Locale.forLanguageTag("en-US"))
}
However, if I need to resolve the locale from session data, there is a problem. ServerWebExchange#getSession() returns Mono<WebSession>. I am not able to get session data in a sync method unless I call Mono#block(). However, this is not an ideal, reactive way.
Is there any way to resolve this? Or are there any plan in Spring to change this?
(There was a similar question previously which got buried: Spring WebFlux - SessionLocaleResolver)

Spring #ControllerAdvice/ #ExceptionHandler not working

My Spring Boot Service will do a job and exit after success with 0 (there is no restcontroller), but i want it aslo to exit on every exception so i added #ControllerAdvice on a class and put this method:
#ControllerAdvice
#RequiredArgsConstructor
#Slf4j
public class ImportInekData {
final InekService inekService;
final ImportDataService dataService;
public void doTheJob(){
log.info("Fetching new list from Inek.");
UpdatedList updatedList = inekService.getUpdatedList();
List<Standort> toBeUpdated = updatedList.getToBeUpdated();
List<String> toBeDeleted = updatedList.getToBeDeleted();
log.info("List fetched with " + toBeUpdated.size() + " valid entries to be updated and " + toBeDeleted.size() + " entries marked for deletion. ");
log.info("Pushing to DB...");
dataService.importAll(toBeUpdated);
}
#EventListener
public void onStart(ContextStartedEvent start){
log.info("Application started.");
doTheJob();
log.info("Import finished.");
SpringApplication.exit(start.getApplicationContext(), () -> 0);
}
#ExceptionHandler(value = Exception.class)
public String outOnException(Exception e){
log.error("Exception occurred see logs. Stopping..");
SpringApplication.exit(context, () -> -1);
return "dying";
}
}
All is working fine but when i throw an IllegalArgumentException the #ExceptionHandler method is not called. First i had a void method with no parameter and then i began trying with String return and at least one parameter - that is not needed.
How get this working? Is there a better way for my case to react on every exception?
Controller Advices in spring is a mechanism intended to properly handle the Exceptions at the level of spring MVC.
Spring MVC in a nutshell is a web framework, and as such, it assumes that you have some kind of web endpoint that is called by the end user or maybe frontend. This endpoint is an "entry-point" to your backend code that can have services, query the database, and so forth. If during this backend flow the exception is thrown in general you don't want that the web endpoint will return 500 internal server error, so spring provides tooling for convenient mapping of these exceptions: translating them to json with a "good-looking" message, with correct HTTP code, and so forth.
If you don't have any controllers, then the whole concept of controller advices is not applicable in your flow, so there is no point in using it...
Now the real question is what exactly do you want to achieve with this exception handling?
If Application context cannot start usually spring boot application will be closed gracefully...
If you want to close the application programmatically, make sure you've read this thread

How to migrate this mutlipart file upload to Spring Boot 2.4?

With Spring Boot 2.3 I was using the following Kotlin code
val mvcResultImage = this.mockMvc!!.perform(MockMvcRequestBuilders.multipart("/somepath)
.file("files[]", imageFile.getBytes())
.characterEncoding("UTF-8"))
.andReturn()
in an integration test for a controller with a function
#PostMapping(path = ["/somepath"],
consumes = [MediaType.MULTIPART_FORM_DATA_VALUE],
produces = [MediaType.APPLICATION_JSON_VALUE])
#ResponseBody
fun createFromBytes(#RequestParam("files[]") file: MultipartFile): ResponseEntity<Any> {
...
}
In 2.3 I was able to handle the request in the controller function whereas in 2.4 the controller function raises a org.springframework.web.multipart.support.MissingServletRequestPartException with the message Required request part 'files[]' is not present and causes HTTP response code 400.
I don't find anything in the migration guide and list of handled issues for this version change.
A rename to file in both controller and request doesn't help, I don't remember why I added [] in the code working with 2.3, but I think it was necessary to make it work.
I'm using Spring Boot through the maven parent mechanism with spring-boot-starter-parent:2.4.1.
This is a known issues in Spring Boot coming from Spring. It's fixed in Spring Boot 2.4.2. The linked issue contains a successfully tested workaround in case you're stuck with 2.4.1: Create MockMultipartFile with MockMultipartFile( String name, #Nullable String originalFilename, #Nullable String contentType, #Nullable byte[] content) (specification of originalFilename matters).

Resolving POST /** request URL to full request URL using micrometer

With the micro-service architecture I have written a generic POST request handler which is consumed by all the micro-services. The post mapping in spring look like this:
#RestController
#RequestMapping(value = "/v1/", consumes = {MediaType.APPLICATION_JSON_VALUE}, produces = {MediaType.APPLICATION_JSON_VALUE})
public class V1Controller {
#PostMapping(path = "/**")
public #ResponseBody Json post () {}
}
Now while I am consuming the metrics for this endpoint using micrometer I am only getting /v1/ as the endpoint in the metrics while I am sending the full URL like /v1/demo/foo from the calling service. I tried lot of the combination but it is not working. I have also added the WebMvcTagsProvider where I am listing to request and resolving the POST api calls.
#Bean
#SuppressWarnings("unchecked")
public WebMvcTagsProvider webMvcTagsProvider(ObjectMapper objectMapper) {
return new DefaultWebMvcTagsProvider() {
public Iterable<Tag> getTags(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable exception) {
if ("POST".equals(request.getMethod())) {
Tag uriTag = Tag.of("uri", String.valueOf(request.getRequestURI()));
return Tags.of(WebMvcTags.method(request), uriTag, WebMvcTags.exception(exception), WebMvcTags.status(response));
}
return Tags.of(WebMvcTags.method(request), WebMvcTags.uri(request, response), WebMvcTags.exception(exception), WebMvcTags.status(response));
}
};
}
Still it is resolving to /v1/ URL in the metrics. I tried googling alot but didn't find any solution. Thanks in advance.
The build in Spring Boot RequestMapping based metrics match on the annotations and add those as tags.
This is to avoid a tag explosion. Imagine a #RequestMapping for a path like user/{userId}, you would want to group all those calls together (user/1, user/2, user/3).
You'll want to create your own Timer in your post method that set that url tags, etc there.
If you decide to reuse the same metric name as the built in Spring Boot metric, you'll want to disable that one as well, so you don't double count those requests.

How to enable Spring Reactive Web MVC to handle Multipart-file?

I'm trying to use the new reactive web-mvc implementation in a spring boot 2.0 application. I'm trying to define a method which consume multipart file but do not succeed at making it working :( - I always get a 415 error.
On one hand I have a controller containing the following request mapping :
#RequestMapping(method = RequestMethod.POST, path = "/myPath/{param}/{param2}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseBody
public Mono<Void> postFile(
#RequestBody MultipartFile data,
#PathVariable("param") String param,
#PathVariable("param2") String param2,
#RequestHeader(name = HEADER_DATE, required = false) #DateTimeFormat(pattern = DATE_FORMAT) Instant instant
){
return fileService.handleData(Mono.just(data), param, param2, instant);
}
On the other hand I had to add a server on the top of the basic dependencies as it seems netty do not handle multipart files. I so added the spring-boot-starter-tomcatdependency which enabled the MultipartAutoConfiguration to be matched and satisfied on application auto configuration.
When posting something using a curl call :
curl 'Meta-Date: 20170101104532' --form "file=#file.bin" http://localhost:8082/myPath/foo/bar
while debug logs are activated (logging.level.org.springframework.web=DEBUG) I got this exception :
org.springframework.web.server.UnsupportedMediaTypeStatusException: Request failure [status: 415, reason: "Content type 'multipart/form-data;boundary=------------------------58fa43b8f1a26de4' not supported"]
This error is thrown by the RequestBodyArgumentResolver which has the the following supported media types : [*/*, text/xml, application/*+json;charset=UTF-8, application/xml, text/plain;charset=UTF-8, application/x-www-form-urlencoded, application/json;charset=UTF-8] provided by 9 DecoderHttpMessageReader.
Before posting I also took a look at :
Spring MultiPart MediaType Unsupported which seems to not be relevant here as my autoconf report contains the following entry : MultipartAutoConfiguration#multipartResolver matched
set content-type to utf-8 with angularjs $http Adding a header setting Content-Transfer-Encoding: binary didn't changed anything.
My understanding is that Spring web 5.0 uses a new request decoder system as I don't find these classes on a spring 4 spring boot application, and there is not yet any DecoderHttpMessageReader dealing with multipart file
Did I miss something ? Or should I wait one to be implemented ?
Okay, It seems this is just not implemented for now as it currently exists a pull request for this feature : Add reactive multipart request support #1201
Should have check this earlier...
[EDIT] : The issue has been solved and merged into Spring master branch. Should no longer be an issue.
#PutMapping(value="/{..}",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Void> save(#RequestPart("file") FilePart multipartFormData,#RequestParam("fileName") String fileName,#PathVariable("..") String ..) throws IOException {
List<ByteBuffer> bytesList = new LinkedList<>();
multipartFormData.content().
subscribe(item->bytesList.add(item.asByteBuffer()));
int totalBytes = bytesList.stream().mapToInt(item->item.capacity()).sum();
ByteBuffer buffer = ByteBuffer.allocate(totalBytes);
bytesList.stream().forEach(byteBuff->buffer.put(byteBuff));
baseImageHandler.saveImage(buffer, fileName, baseItemId);
return Mono.empty();
}
Please note that it is a dev verison, but this is how I have managed to do it.

Resources