From 09ae080b99c454a2020100eecd0c1bff241cdf3a Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 4 Mar 2025 15:18:17 +0000 Subject: [PATCH 1/2] isDisconnectedClientException protected for null Closes gh-34533 --- .../springframework/web/util/DisconnectedClientHelper.java | 7 ++++++- .../web/util/DisconnectedClientHelperTests.java | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java b/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java index a62f6312bbb..5779f3ec6b2 100644 --- a/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java +++ b/spring-web/src/main/java/org/springframework/web/util/DisconnectedClientHelper.java @@ -24,6 +24,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.NestedExceptionUtils; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -100,7 +101,11 @@ public class DisconnectedClientHelper { *
  • IOException "Broken pipe" or "connection reset by peer" * */ - public static boolean isClientDisconnectedException(Throwable ex) { + public static boolean isClientDisconnectedException(@Nullable Throwable ex) { + if (ex == null) { + return false; + } + Throwable currentEx = ex; Throwable lastEx = null; while (currentEx != null && currentEx != lastEx) { diff --git a/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java b/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java index 296a1920927..0d79da9ebf4 100644 --- a/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/DisconnectedClientHelperTests.java @@ -90,4 +90,9 @@ public class DisconnectedClientHelperTests { assertThat(DisconnectedClientHelper.isClientDisconnectedException(ex)).isFalse(); } + @Test // gh-34533 + void nullException() { + assertThat(DisconnectedClientHelper.isClientDisconnectedException(null)).isFalse(); + } + } From 9ab43b138aafca1dfbdfe4b300cf25f5b5a9e1db Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 6 Mar 2025 11:03:22 +0000 Subject: [PATCH 2/2] Enhancement in HandlerMethodValidationException Add dedicated method to Visitor for constraints directly on a RequestBody method parameter (rather than nested). Closes gh-34549 --- .../HandlerMethodValidationException.java | 23 +++++++++++++++++-- ...HandlerMethodValidationExceptionTests.java | 15 ++++++++---- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java index 019576c4d1a..4f62a3fa403 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/HandlerMethodValidationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -146,7 +146,12 @@ public class HandlerMethodValidationException extends ResponseStatusException im } RequestBody requestBody = param.getParameterAnnotation(RequestBody.class); if (requestBody != null) { - visitor.requestBody(requestBody, asErrors(result)); + if (result instanceof ParameterErrors errors) { + visitor.requestBody(requestBody, errors); + } + else { + visitor.requestBodyValidationResult(requestBody, result); + } continue; } RequestHeader requestHeader = param.getParameterAnnotation(RequestHeader.class); @@ -216,6 +221,20 @@ public class HandlerMethodValidationException extends ResponseStatusException im */ void requestBody(RequestBody requestBody, ParameterErrors errors); + /** + * An additional {@code @RequestBody} callback for validation failures + * for constraints on the method parameter. For example: + *
    +		 * @RequestBody List<@NotEmpty String> ids
    +		 * 
    + * Handle results for {@code @RequestBody} method parameters. + * @param requestBody the annotation declared on the parameter + * @param result the validation result + * @since 7.0 + */ + default void requestBodyValidationResult(RequestBody requestBody, ParameterValidationResult result) { + } + /** * Handle results for {@code @RequestHeader} method parameters. * @param requestHeader the annotation declared on the parameter diff --git a/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java b/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java index c14791d7721..f30f8cbeabe 100644 --- a/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/support/HandlerMethodValidationExceptionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.Size; import org.junit.jupiter.api.Test; @@ -67,7 +68,7 @@ class HandlerMethodValidationExceptionTests { private final HandlerMethod handlerMethod = handlerMethod(new ValidController(), - controller -> controller.handle(person, person, person, person, "", "", "", "", "", "")); + controller -> controller.handle(person, person, person, List.of(), person, "", "", "", "", "", "")); private final TestVisitor visitor = new TestVisitor(); @@ -84,7 +85,7 @@ class HandlerMethodValidationExceptionTests { assertThat(this.visitor.getOutput()).isEqualTo(""" @ModelAttribute: modelAttribute1, @ModelAttribute: modelAttribute2, \ - @RequestBody: requestBody, @RequestPart: requestPart, \ + @RequestBody: requestBody, @RequestBody: requestBodyList, @RequestPart: requestPart, \ @RequestParam: requestParam1, @RequestParam: requestParam2, \ @RequestHeader: header, @PathVariable: pathVariable, \ @CookieValue: cookie, @MatrixVariable: matrixVariable"""); @@ -100,7 +101,7 @@ class HandlerMethodValidationExceptionTests { assertThat(this.visitor.getOutput()).isEqualTo(""" Other: modelAttribute1, @ModelAttribute: modelAttribute2, \ - @RequestBody: requestBody, @RequestPart: requestPart, \ + @RequestBody: requestBody, @RequestBody: requestBodyList, @RequestPart: requestPart, \ Other: requestParam1, @RequestParam: requestParam2, \ @RequestHeader: header, @PathVariable: pathVariable, \ @CookieValue: cookie, @MatrixVariable: matrixVariable"""); @@ -155,6 +156,7 @@ class HandlerMethodValidationExceptionTests { @Valid Person modelAttribute1, @Valid @ModelAttribute Person modelAttribute2, @Valid @RequestBody Person requestBody, + @RequestBody List<@NotEmpty String> requestBodyList, @Valid @RequestPart Person requestPart, @Size(min = 5) String requestParam1, @Size(min = 5) @RequestParam String requestParam2, @@ -222,6 +224,11 @@ class HandlerMethodValidationExceptionTests { handle(requestBody, errors); } + @Override + public void requestBodyValidationResult(RequestBody requestBody, ParameterValidationResult result) { + handle(requestBody, result); + } + @Override public void requestHeader(RequestHeader requestHeader, ParameterValidationResult result) { handle(requestHeader, result);