Spring Boot: How to validate all parameters in a RestController - spring

Say I have a #RestController with 10 methods, each of which takes one or more parameters. How can I tell Spring to validate each and every one of those parameters without annotating all of them with #Valid?
I already tried annotating the whole class with #Validated but to no effect. Maybe I have missed a necessary configuration?
The controller:
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
#Validated
#RestController
class EntityController {
#PutMapping("/{id}")
fun update(#PathVariable id: UUID, #RequestBody entityDto: EntityDto) {
// update the entity
}
// more methods
}
and the DTO:
import javax.validation.constraints.NotBlank
data class EntityDto(
#field:NotBlank
private val name: String
)
It works perfectly well if I add #Valid to the #RequestBody annotation at method level. Then requests like PUT /123 { "name": " " } are rejected because of the blank name field.
Any clues as to how I get my controller to validate every object regardless of #Valid annotation?

You can create a class extending javax.validation.ConstraintValidator and writing field validation logic for all the field inside it. Take a look at this example
https://www.baeldung.com/javax-validation-method-constraints

Related

Spring Data Rest - validate Bean before save

I've been searching for the simplest and best way to validate my entities before they are created/updated and after much googling, I couldn't find a clean/modern one.
Ideally, I would have loved to be able to use #Valid as follows:
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
#Slf4j
#Validated
#Component
#RepositoryEventHandler
public class CustomerEventHandler {
// Triggered for POST
#HandleBeforeCreate
public void onBeforeCreate(#Valid Customer entity) {
log.info("Saving new entity {}", entity);
}
// Triggered for PUT / PATCH
#HandleBeforeSave
public void onBeforeSave(#Valid Customer entity) {
log.info("Saving new entity {}", entity);
}
}
The Customer entity being:
import javax.validation.constraints.NotBlank;
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "customer")
public class Customer {
#NotBlank
private String firstname;
}
But it doesn't seem to work.
What's the modern, easy way to validate entities in Spring Data REST?
Note: I'm using Spring Boot
I checked your pom.xml in linked GitHub project. You have just a dependency to validation annotations, but the proper way with Spring Boot is to use the spring-boot-starter-validation Dependency. The Spring Boot Starter Dependencies add the "magic" to your project, which triggers automatically the validation based on your annotations.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
I my German blog I have written an article about this topic:
https://agile-coding.blogspot.com/2020/11/validation-with-spring.html
I want to suggest a few best practise that every developer, who starting as junior/beginner should be know. Don't put any Validation annotation in Entities, using them in DTO/Resource classes. and the best way to practise validation is that you can handler MethodArgumentNotValidation exception in your own Spring Boot of Exception Handler class annotated that class #RestControllerAdvice and create your own #interface annotation instead of using more validation annotation.

Spring injects a bean other than what is specified in #Configuration

Recently I've made an error while wiring beans in Spring that caused a behaviour that I'm unable to replicate. Instead of a property sourced with #Value getting injected into Stuff (see the complete demo code below) a value of another bean of type String defined in #Configuration was used when the application was deployed.
What I find puzzling is that everything works as expected when running locally (including the unit test), the output is foo not kaboom, and that this 'bean swap' happened at all when deployed rather than 'no qualifying bean' error.
The commented out line shows the fix which I think makes the configuration similar to what is in the manual.
What is the problem with my set-up? What would make the code as shown (i.e. without the fix) use kaboom String rather than foo property?
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
#SpringBootApplication
open class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
#Configuration
open class Config {
// ...beans of types other than String in original code...
#Bean
open fun beanBomb(): String {
return "kaboom"
}
#Bean
// fix:
// #Value("\${stuff}")
open fun beanStuff(stuff: String): Stuff {
return Stuff(stuff)
}
}
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
#Component
class Stuff(#Value("\${stuff}") val stuff: String)
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import javax.annotation.PostConstruct
#Component
class Init {
#Autowired
private lateinit var stuff: Stuff
#PostConstruct
fun init() {
println("stuff: " + stuff.stuff)
}
}
// application.properties
stuff=foo
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
#ExtendWith(SpringExtension.class)
#TestPropertySource(properties = {"stuff=testFoo"})
class DemoApplicationTests {
#SpyBean
private Stuff stuff;
#Test
void test() {
assertEquals("testFoo", stuff.getStuff());
}
}
Also, is the #Value annotation in Stuff necessary once the fix has been applied? If I uncomment the fix, remove #Value from Stuff and add the following annotation to the test class the test passes:
#ContextConfiguration(classes = {Config.class})
but when I run the app it prints kaboom...
You can check the order in which the bean is being created. if the bean is created before than in my view the Spring IoC container inject the value by type i.e. kaboom and since the Bean of any type is singleton by default, the instance of Stuff won't come into effect even though it is annotated with #component.
In your test you're loading the configuration manually where the bean of Stuff defined in Config is being injected not the Stuff annotated with #component.
The problem is the annotation needs to go on the parameter not the function.
In your way Spring is looking for a bean that meets the Type of String and there is a bean of Type String produced by the function beanBomb(). If you move the annotation like this it should remove the ambiguity.
#Bean
open fun beanStuff(#Value("\${stuff}") stuff: String): Stuff {
return Stuff(stuff)
}
I would add tho, that it's a bit unusual to have a bean of Type String, but I suppose if you don't want to use property/yaml files it would allow you to change a String based on profile.

Javax validation not working on boot rest controller parameter

I have a Pojo (annotated with Lombok if it makes any difference) and a RestController in a Spring Boot app. The pojo method parameter is annotated with #Valid yet there is no validation applied and even if I add a BindingResult second parameter, it never has any error. I'm testing this with Swagger UI, posting a JSON, but I don't see why that would make any differnce... Did I miss something obvious?
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Tolerate;
import org.springframework.data.mongodb.core.mapping.Document;
import javax.validation.constraints.NotBlank;
#Data
#EqualsAndHashCode(callSuper = true)
#Builder
public class MyPojo extends GenericDocument {
#NotBlank
private String name;
#Tolerate
public MyPojo() {
}
}
import javax.validation.Valid;
#RestController
#RequestMapping(value = "/myUrl", produces = APPLICATION_JSON_VALUE)
public class MyController {
#PostMapping
public ResponseEntity<MyPojo> createNew(#RequestBody #Valid MyPojo pojo){...
}
}
Solution
Adding this dependency was enough:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
It is strange that adding javax.validation:validation-api and org.hibernate:hibernate-validator was not enough despite the documentation
You should try adding Spring’s #Validated annotation to the controller at class level to tell Spring to evaluate the constraint annotations on method parameters.
Example:
#Slf4j
#Validated
#RestController
#RequestMapping(value = "/myUrl", produces = APPLICATION_JSON_VALUE)
public class MyController {
#PostMapping
public ResponseEntity<MyPojo> createNew(#RequestBody #Valid MyPojo pojo){...
}
}
This org.springframework.validation.annotation.Validated annotation can be applied at both class level and method or parameter level.
Per the documentation, which is not entirely clear, you need to have a JSR-303 validation provider such as Hibernate Validator (docs here) on your classpath so Spring can detect and use it.
It also seems in some cases you may need the Spring Boot Starter Validation dependency (spring-boot-starter-validation) on your classpath as well. I would suggest trying that to see if it helps. (Strangely, though, I don't see that detailed in the Spring docs - just briefly mentioned here.)
Or you could maybe implement the Validator interface as per here, but not sure.

Custom annotation on controller methods to intercept request and validate

I want to create an annotation that I will use on controller methods to validate access to the resource. I have written interceptor to intercept the request and also written code to create an annotation for their independent scenarios. Now I want intercept the request as well as take values provided in anotation to further processing.
Ideally
#RequestMapping("/release")
#ValidateAction("resource","release") //custom annotation that will accept two strings
public ResponseEntity releaseSoftware(Request request){
}
From the above I have to take those two values from #ValidateAction and send a request to another authorization server to authorize the action if the user have access to it (request contains oauth access token that will be used to authorize) and return true if the user have access otherwise throw AcceeDenied exception.
Can anybody point me in the right direction of doing it in Spring boot environment
Best way to achieve this is using Spring AOP Aspects.
Let us assume you have an Annotation like this
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateAction {
String resource();
String release();
}
Then write an Aspect like this
#Aspect
#Component
public class AspectClass {
#Around(" #annotation(com.yourpackage.ValidateAction)")
public Object validateAspect(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
ValidateAction validateAction = method.getAnnotation(ValidateAction.class);
String release = validateAction.release();
String resource = validateAction.resource();
// Call your Authorization server and check if all is good
if( hasAccess)
pjp.proceed();
.......
}
}
Control will come to validateAspect method when any method which is annotated with #ValidateAction is called. Here you capture the annotation values as shown and do the necessary check.
Make sure you have the right dependency needed and these imports
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
You can do it with Spring AOP:
First, add spring-aop dependency:
compile 'org.springframework.boot:spring-boot-starter-aop' // Mine using gradle
In your #Configuration class, add #EnableAspectJAutoProxy:
#Configuration
#EnableAspectJAutoProxy
public class Application {
....
}
Create an annotation handler:
#Aspect
#Component // This #Component is required in spring aop
public class ValidateActionHandler {
#Around("execution(#your.path.ValidateAction * *(..)) && #annotation(validateAction)")
public Object doValidate(ProceedingJoinPoint pjp, ValidateAction retryConfig) throws Throwable {
// Your logic here
// then
return pjp.proceed();
}
}

Generic JPA repository for runtime generated entities

In my scenario im generating hibernate entity classes at runtime under "com.mrg.domain" package. And in my generic restcontroller i can create instances of these entities according to #PathVariable. Below code works fine for this..
#RequestMapping( value = "/{entity}", method = RequestMethod.POST)
public #ResponseBody RestResponse createEntity(#PathVariable String entity, #RequestBody String requestBody) {
Object model = null;
ObjectMapper mapper = new ObjectMapper();
try {
// ex : if {entitiy} param is equal "post" modelName will be "Post"
String modelName = Character.toUpperCase(entity.charAt(0)) + entity.substring(1);
// Creating a new instance according to modelName
Class<?> clazz = Class.forName("com.mrg.domain." + modelName);
model = clazz.newInstance();
// Converting #RequestBody json String to domain object..
model = mapper.readValue(requestBody, clazz);
} catch(Exception ex){
// TODO handle exceptions & inform user..
}
return new RestResponse(model.toString());
}
Now the next step i am trying to implement is a generic jpa repository(something like below) so that i can persist runtime generated models without implementing repositories for each entity. But couldn't find a solution yet.
#Repository
public interface GenericRepository<T> extends PagingAndSortingRepository<T, Long>{ }
Below topic and many other topics implemented generic repositories but also repositories per entities that uses generic repo. Since i have runtime generated entities repo implementation per entity doesnt work for me..
How to make generic jpa repository? Should I do this? Why?
Any suggestion or a way for achieving this? I'm new to generics and reflection so if what im trying to accomplish is not possible, tell me reasons and i would be appreciate..
Thanks and regards,
You could use this pattern. This one uses EJB but can be used in Spring etc.
#Stateless
public abstract class AbstractRepository<T> {
#PersistenceContext
protected EntityManager em;
public abstract Class<T> getActualClass();
public T getSingleResult(Map<String, String> params) {
// build querytext based on params
TypedQuery<T> query = em.createQuery(queryText.toString(), getActualClass());
............
}
}
Now for the implementation class:
#Stateless
public class InputStreamRepository extends AbstractRepository<InputDataStream> {
#Override
public Class<InputDataStream> getActualClass() {
return InputDataStream.class;
}
}
The getActualClass method will give you the Entity's class impl info.
I had a react application where different data is defined in JSON and in the server side, I need to store this in the DB. My initial approach was to create entities , repositories and controller for all of this seperately. But another possible approach for CRUD operation is with MongoDB & JPA. Here is the idea.
import java.util.List;
import org.bson.Document;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/api/generic")
#CrossOrigin(origins = { "*" })
public class GenericController {
#Autowired
private MongoTemplate mongoTemplate;
#PostMapping
public ResponseEntity<Document> addData(#RequestBody String data) {
JSONObject jsonObject = new JSONObject(data);
String documentName = jsonObject.getString("documentName");
Document doc = Document.parse(data);
Document insertedDoc = mongoTemplate.insert(doc, documentName);
return new ResponseEntity<>(insertedDoc, HttpStatus.CREATED);
}
#GetMapping("/{documentName}")
public List<Document> getData(#PathVariable String documentName) {
List<Document> allData = mongoTemplate.findAll(Document.class, documentName);
return allData;
}
}

Resources