Skip to content

Commit 04c9c16

Browse files
committed
Merge pull request #37119 from zpavloudis
* gh-37119: Polish "Support unwrapping in ValidatorAdapter" Support unwrapping in ValidatorAdapter Closes gh-37119
2 parents 0242ba8 + 4085425 commit 04c9c16

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
*
4040
* @author Stephane Nicoll
4141
* @author Phillip Webb
42+
* @author Zisis Pavloudis
4243
* @since 2.0.0
4344
*/
4445
public class ValidatorAdapter implements SmartValidator, ApplicationContextAware, InitializingBean, DisposableBean {
@@ -153,4 +154,13 @@ private static Validator wrap(Validator validator, boolean existingBean) {
153154
return validator;
154155
}
155156

157+
@Override
158+
@SuppressWarnings("unchecked")
159+
public <T> T unwrap(Class<T> type) {
160+
if (type.isInstance(this.target)) {
161+
return (T) this.target;
162+
}
163+
return this.target.unwrap(type);
164+
}
165+
156166
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidatorAdapterTests.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import java.util.HashMap;
2020

21+
import jakarta.validation.Validator;
2122
import jakarta.validation.constraints.Min;
23+
import org.hibernate.validator.HibernateValidator;
2224
import org.junit.jupiter.api.Test;
2325

2426
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -27,10 +29,13 @@
2729
import org.springframework.context.annotation.Bean;
2830
import org.springframework.context.annotation.Configuration;
2931
import org.springframework.core.io.ClassPathResource;
32+
import org.springframework.validation.Errors;
3033
import org.springframework.validation.MapBindingResult;
34+
import org.springframework.validation.SmartValidator;
3135
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
3236

3337
import static org.assertj.core.api.Assertions.assertThat;
38+
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
3439
import static org.mockito.ArgumentMatchers.any;
3540
import static org.mockito.BDDMockito.then;
3641
import static org.mockito.Mockito.mock;
@@ -91,6 +96,30 @@ void wrapperWhenValidationProviderNotPresentShouldNotThrowException() {
9196
.run((context) -> ValidatorAdapter.get(context, null));
9297
}
9398

99+
@Test
100+
void unwrapToJakartaValidatorShouldReturnJakartaValidator() {
101+
this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> {
102+
ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class);
103+
assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class);
104+
});
105+
}
106+
107+
@Test
108+
void whenJakartaValidatorIsWrappedMultipleTimesUnwrapToJakartaValidatorShouldReturnJakartaValidator() {
109+
this.contextRunner.withUserConfiguration(DoubleWrappedConfig.class).run((context) -> {
110+
ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class);
111+
assertThat(wrapper.unwrap(Validator.class)).isInstanceOf(Validator.class);
112+
});
113+
}
114+
115+
@Test
116+
void unwrapToUnsupportedTypeShouldThrow() {
117+
this.contextRunner.withUserConfiguration(LocalValidatorFactoryBeanConfig.class).run((context) -> {
118+
ValidatorAdapter wrapper = context.getBean(ValidatorAdapter.class);
119+
assertThatRuntimeException().isThrownBy(() -> wrapper.unwrap(HibernateValidator.class));
120+
});
121+
}
122+
94123
@Configuration(proxyBeanMethods = false)
95124
static class LocalValidatorFactoryBeanConfig {
96125

@@ -106,6 +135,55 @@ ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) {
106135

107136
}
108137

138+
@Configuration(proxyBeanMethods = false)
139+
static class DoubleWrappedConfig {
140+
141+
@Bean
142+
LocalValidatorFactoryBean validator() {
143+
return new LocalValidatorFactoryBean();
144+
}
145+
146+
@Bean
147+
ValidatorAdapter wrapper(LocalValidatorFactoryBean validator) {
148+
return new ValidatorAdapter(new Wrapper(validator), true);
149+
}
150+
151+
static class Wrapper implements SmartValidator {
152+
153+
private final SmartValidator delegate;
154+
155+
Wrapper(SmartValidator delegate) {
156+
this.delegate = delegate;
157+
}
158+
159+
@Override
160+
public boolean supports(Class<?> clazz) {
161+
return this.delegate.supports(clazz);
162+
}
163+
164+
@Override
165+
public void validate(Object target, Errors errors) {
166+
this.delegate.validate(target, errors);
167+
}
168+
169+
@Override
170+
public void validate(Object target, Errors errors, Object... validationHints) {
171+
this.delegate.validate(target, errors, validationHints);
172+
}
173+
174+
@Override
175+
@SuppressWarnings("unchecked")
176+
public <T> T unwrap(Class<T> type) {
177+
if (type.isInstance(this.delegate)) {
178+
return (T) this.delegate;
179+
}
180+
return this.delegate.unwrap(type);
181+
}
182+
183+
}
184+
185+
}
186+
109187
@Configuration(proxyBeanMethods = false)
110188
static class NonManagedBeanConfig {
111189

0 commit comments

Comments
 (0)