How do I make Spring's #Validated use a custom validator? - java

E.g. let's say I had a domain class, a custom validator, and a controller kind of like this:
public class MyDomain {
/*Stuff that I want to validate*/
}
public class MyValidator extends LocalValidatorFactoryBean {
/*Custom Validation Logic*/
}
#RestController
public class myController {
#RequestMapping(path = "/myDomain", method=RequestMethod.POST)
public void doStuff(#RequestBody #Validated MyDomain myDomain){
//Do stuff.
}
}
How do I make it so that when validation happens on #Validated MyDomain myDomain that MyValidator is used?
Also, can I use a subclass of org.springframework.validation.beanvalidation.LocalValidatorFactoryBean for this or do I need to implement org.springframework.validation.Validator?

Related

Inject multiple beans of the same type and automatically select between them based on generic type

I have two (more in the future) implementations of ImportantService – VeryImportantService and LessImportantService:
public interface ImportantService<T extends ImportantRequest> {}
#Service
public class VeryImportantService implements ImportantService<VeryImportantRequest> {}
#Service
public class LessImportantService implements ImportantService<LessImportantRequest> {}
And then I have a controller, in which I want to inject all of the implementations of ImportantService:
#RequiredArgsConstructor
#RestController
#RequestMapping("/api/important")
public class ImportantController<T extends ImportantRequest> {
private final ImportantService<T> importantService;
#PostMapping
public ResponseEntity<ImportantResponse> create(#RequestBody #Valid T request) {
// very important code here
}
}
Obviously, such king of injecting fails:
UnsatisfiedDependencyException: Error creating bean with name 'importantController' defined in file ...
...
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
What I want is:
Inject all of the implementations of ImportantService, and then, based on the T automatically select required bean. I know I can add method to ImportantService, which returns the type that implementation works with and then inject ImportantService as List<ImportantService> importantServices and then filter like this:
importantServices.stream()
.filter(importantService -> importantService.getType().equals(request.getClass()))
.findFirst()
.ifPresent(importantService -> importantService.doImportantJob(request));
BUT! I have hundreds of services to refactor like this and I really don't want to write additional logic to controllers.
I know about #Conditional annotation and Condition interface, but AFAIK there's no way to make them do what I want.
Why not implement the proxy pattern?
example:
#Service
#Primary
#RequiredArgsConstructor
public class ImportantServiceProxy implements ImportantService<T extends ImportantRequest> {
private final List<ImportantService> importantServices;
private ImportantService getImportantService(ImportantRequest request){
return this.importantServices.stream()
.filter(importantService -> importantService.getType().equals(request.getClass()))
.findFirst()
.get();
}
public void doImportantJob(ImportantRequest request){
this.getImportantService(request).doImportantJob(request);
}
}
Then in your controller you can call the function without check the type.
#RequiredArgsConstructor
#RestController
#RequestMapping("/api/important")
public class ImportantController<T extends ImportantRequest> {
private final ImportantService<T> importantService;
#PostMapping
public ResponseEntity<ImportantResponse> create(#RequestBody #Valid T request) {
importantService.doImportantJob(request);
}
}
what you want is a list of beans which are of type ImportantService
so you have to declare a variable like this.
final List<ImportantService> importantServices;
demoController(List<ImportantService> importantServices) {
this.importantServices = importantServices;
}

Spring Boot application.properties custom variable in a non-controller class

How come application.properties will work in a RestController, but not in a service class?
//application.properties
test=test
Works Perfect!
#RestController
public class invitecontroller {
#Autowired inviteconfig inviteconfig;
#PostMapping("/v1/invite")
public void invite(#RequestBody XXX XXX) {
System.out.println(inviteconfig);
}
}
Returns "Null"
#Service
public class inviteservice {
#Autowired inviteconfig inviteconfig;
public void invite() {
System.out.println(inviteconfig);
}
}
#Configuration
#Data
public class inviteconfig {
private String test;
}
The inviteservice class is not configured for Spring IoC (Inversion of Control) as a bean, so Spring will not handle the inviteservice class lifecycle. In this case, #Autowired is useless.
To fix this try to add #Component annotation to invitesevice, to declare it as a component:
#Component
public class inviteservice {
#Autowired inviteconfig inviteconfig;
public void invite() {
System.out.println(inviteconfig);
}
}
In the case of the controller, with #RestController, Spring will recognize your class as a Spring component.
Finally, don't forget to inject inviteservice using Spring IoC (using #Autowired annotation, or other means)
inviteservice class should be annotated with #Component or #Service
#Component
public class inviteservice {
...

Spring Security Principal transformation

Is there a way to transform a Spring Security Principal before it is injected in a RestController method?
Let's say I have defined the following class:
#RestController
public class MyController {
#GetMapping("/test")
public void getWithPrincipalA(#AuthenticationPrincipal PrincipalTypeA a) {
...
}
#GetMapping("/test")
public void getWithPrincipalB(#AuthenticationPrincipal PrincipalTypeB b) {
...
}
}
I know that these controller methods are ambiguous and I could do several things to solve that, but what I would rather do is transform the #AuthenticationPrincipal to some type I can define myself. The result would become something like:
#RestController
public class MyController {
#GetMapping("/test")
public void getWithTransformedPrincipal(#AuthenticationPrincipal MyTransformedPrincipal principal) {
...
}
}
Now I basically could define a single controller for several different authentication principals, without having to change the API.
Any help would be appreciated :)
Too keep things simple and transparant you could simply transform the principal in your controller method and dispatch the generic principal from there.
#RestController
public class MyController {
#GetMapping("/test")
public void getWithTransformedPrincipal(#AuthenticationPrincipal Principal principal) {
GenericPrincipal generic = PrincipalTransformer.transform(principal);
doSomethingWithPrincipal(generic);
}
}

Is that possible to run validation code before Populating Spring #Value

i have spring MVC controller
#Controller
#RequestMapping({ "/user/limits" })
public class UserController {
#Value("${wsgServiceURL}")
private String wsgServiceURL;
.
.
which populate wsgServiceURL value from property file
is that possible to run validation code on that value before population
Yes it is possible using type safe configuration properties through the #ConfigurationPropertiesmechanism
#Controller
#RequestMapping({ "/user/limits" })
#ConfigurationProperties("uc")
public class UserController {
// will map to uc.wsgServiceURL in property file
private String wsgServiceURL;
You can also add validation with #Validated and use use JSR-303 javax.validation
you can do something like the below,
#Controller
#RequestMapping({ "/user/limits" })
public class UserController {
private String wsgServiceURL;
#Autowired
public void initProperty(#Value("${wsgServiceURL}") String wsgServiceURL) {
if(wsgServiceURL== null) {
// Error handling here
}
}
}

How to test if #Valid annotation is working?

I have the following unit test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {EqualblogApplication.class})
#WebAppConfiguration
#TestPropertySource("classpath:application-test.properties")
public class PostServiceTest {
// ...
#Test(expected = ConstraintViolationException.class)
public void testInvalidTitle() {
postService.save(new Post()); // no title
}
}
The code for save in PostService is:
public Post save(#Valid Post post) {
return postRepository.save(post);
}
The Post class is marked with #NotNull in most fields.
The problem is: no validation exception is thrown.
However, this happens only in testing. Using the application normally runs the validation and throws the exception.
Note: I would like to do it automatically (on save) and not by manually validating and then saving (since it's more realistic).
This solution works with Spring 5. It should work with Spring 4 as well. (I've tested it on Spring 5 and SpringBoot 2.0.0).
There are three things that have to be there:
in the test class, provide a bean for method validation (PostServiceTest in your example)
Like this:
#TestConfiguration
static class TestContextConfiguration {
#Bean
public MethodValidationPostProcessor bean() {
return new MethodValidationPostProcessor();
}
}
in the class that has #Valid annotations on method, you also need to annotate it with #Validated (org.springframework.validation.annotation.Validated) on the class level!
Like this:
#Validated
class PostService {
public Post save(#Valid Post post) {
return postRepository.save(post);
}
}
You have to have a Bean Validation 1.1 provider (such as Hibernate Validator 5.x) in the classpath. The actual provider will be autodetected by Spring and automatically adapted.
More details in MethodValidationPostProcessor documentation
Hope that helps
This is how I did it by loading ValidationAutoConfiguration.class into context:
#SpringBootTest
#ContextConfiguration(classes = { MyComponent.class, ValidationAutoConfiguration.class
public class MyComponentValidationTest {
#Autowired
private MyComponent myComponent;
#Test
void myValidationTest() {
String input = ...;
// static import from org.assertj.core.api.Assertions
assertThatThrownBy(() -> myComponent.myValidatedMethod(input))
.isInstanceOf(ConstraintViolationException.class)
.hasMessageContaining("my error message");
}
}
And MyComponent class:
#Component
#Validated
public class MyComponent {
public void myValidatedMethod(#Size(min = 1, max = 30) String input) {
// method body
}
)

Categories