Using Spring Boot, I've created an example application.
package hello;
import org.springframework.web.bind.annotation.RestController;
import constraint.CheckHelloId;
import dto.HelloDto;
import javax.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
#RestController
public class HelloController {
#RequestMapping(value = "/", method = RequestMethod.POST)
public String index(#RequestBody #Valid final HelloDto hello) {
hello.setId(null);
validateFromMethodHeader(hello);
return "Greetings from Spring Boot!";
}
private void validateFromMethodHeader(#CheckHelloId final HelloDto helloDto) {
System.out.println("Validating DTO...");
}
}
I'm trying to add a custom constraint to the DTO HelloDto in the private method that checks if the id field is null or not.
The interface:
package constraint;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Target({ FIELD, ANNOTATION_TYPE, TYPE, METHOD, PARAMETER })
#Retention(RUNTIME)
#Constraint(validatedBy = CheckHelloIdValidator.class)
#Documented
public #interface CheckHelloId {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
The validator:
package constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
import org.apache.commons.lang3.StringUtils;
import dto.HelloDto;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class CheckHelloIdValidator implements ConstraintValidator<CheckHelloId, HelloDto> {
private static final String ID_VALIDATION_ERROR = "Null or blank ID.";
#Override
public void initialize(CheckHelloId arg0) {
}
#Override
public boolean isValid(HelloDto helloDto, ConstraintValidatorContext context) {
if (StringUtils.isBlank(helloDto.getId())){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(ID_VALIDATION_ERROR).addConstraintViolation();
return false;
}
return true;
}
}
The DTO:
package dto;
import java.io.Serializable;
public class HelloDto implements Serializable {
private static final long serialVersionUID = 8792903048191496378L;
private String id;
private String message;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
As seen in the private method at the controller, I'm trying to apply the validation of the DTO at a parameter level (I know I can just do it at the index via entity validation, but I want to test this concrete case, that's why I set the id field as null at the controller).
When I run the application and perform the call, the constraint does not apply in the private method, even when the id field is null. Could anybody shed some light on this? Thank you in advance.
To validate consuming json on rest controller beside annotated method parameters #RequestBody #Valid final HelloDto hello you have to annotate filed of your json data class with special constraints. For you case it enough to use hibernate validation constraints #NotNull or #NotEmpty:
package dto;
import java.io.Serializable;
public class HelloDto implements Serializable {
private static final long serialVersionUID = 8792903048191496378L;
#NotEmpty
private String id;
Related
I'm building a Spring Boot app using CosmosDB as my database. All functions work (creating an item, updating one, get all, get by id,...), apart from delete functions. They don't do anything and since their output is void, it doesn't give me any logs either.
The DAO class:
package projects.trashcanapplication.trashcan.dao;
import com.azure.spring.data.cosmos.core.mapping.Container;
import com.azure.spring.data.cosmos.core.mapping.GeneratedValue;
import com.azure.spring.data.cosmos.core.mapping.PartitionKey;
import org.springframework.data.annotation.Id;
import projects.trashcanapplication.trashcan.models.Address;
import projects.trashcanapplication.trashcan.models.FillStatus;
#Container(containerName = "trashcanData")
public class TrashcanDao{
#GeneratedValue
private String attachments;
private FillStatus fillStatus;
#GeneratedValue
private String rid;
private Address address;
#Id
#PartitionKey
#GeneratedValue
private String id;
#GeneratedValue
private String self;
#GeneratedValue
private String etag;
#GeneratedValue
private int ts;
public TrashcanDao(Address address, FillStatus fillStatus) {
this.fillStatus = fillStatus;
this.address = address;
}
public String getAttachments(){
return attachments;
}
public FillStatus getFillStatus(){
return fillStatus;
}
public String getRid(){
return rid;
}
public Address getAddress(){
return address;
}
public String getId(){
return id;
}
public String getSelf(){
return self;
}
public String getEtag(){
return etag;
}
public int getTs(){
return ts;
}
}
The repository
package projects.trashcanapplication.trashcan.repositories;
import com.azure.spring.data.cosmos.repository.ReactiveCosmosRepository;
import projects.trashcanapplication.trashcan.dao.TrashcanDao;
public interface TrashcanRepository extends ReactiveCosmosRepository<TrashcanDao, String> {
}
The service calling the repository
package projects.trashcanapplication.trashcan.services;
import com.azure.cosmos.models.PartitionKey;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import projects.trashcanapplication.trashcan.dao.TrashcanDao;
import projects.trashcanapplication.trashcan.models.Trashcan;
import projects.trashcanapplication.trashcan.repositories.TrashcanRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
#Slf4j
#Service
#AllArgsConstructor
public class TrashcanServiceImpl implements TrashcanService {
private final TrashcanRepository trashcanRepository;
private final TrashcanMapper trashcanMapper;
public Flux<Trashcan> getAllTrashcans() {
return trashcanRepository.findAll().map(trashcanMapper::fromDaoToTrashcan);
}
public Mono<Trashcan> getTrashcanById(String id) {
return trashcanRepository.findById(id).map(trashcanMapper::fromDaoToTrashcan);
}
public String createTrashcan(Trashcan trashcan) {
TrashcanDao saveTrashcan = trashcanMapper.fromTrashcanToDao(trashcan);
trashcanRepository.save(saveTrashcan).subscribe();
return saveTrashcan.getId();
}
public void deleteTrashcan(String id) {
trashcanRepository.deleteById(id, new PartitionKey(id));
log.info(String.format("Deleted trashcan %s", id));
}
}
I have a dataloader temporarily set up to populate my DB with an item upon running the app. The deleteAll() function doesn't work here either.
package projects.trashcanapplication.trashcan.services;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import projects.trashcanapplication.trashcan.dao.TrashcanDao;
import projects.trashcanapplication.trashcan.models.Address;
import projects.trashcanapplication.trashcan.models.FillStatus;
import projects.trashcanapplication.trashcan.repositories.TrashcanRepository;
import javax.annotation.PostConstruct;
#Slf4j
#Component
#AllArgsConstructor
public class DataLoader {
private final TrashcanRepository trashcanRepository;
#PostConstruct
void loadData() {
Address address1 = new Address("Begijnendijk", "3130", "Liersesteenweg", "181");
trashcanRepository.deleteAll();
trashcanRepository.save(new TrashcanDao(address1, FillStatus.EMPTY))
.flatMap(trashcanRepository::save)
.thenMany(trashcanRepository.findAll())
.subscribe(trashcan -> log.info(trashcan.getId().toString()))
;
}
}
You're not subscribing anywhere, so the reactive stream isn't executed.
You could solve that by subscribing manually:
trashcanRepository.deleteAll().subscribe()
However, this is not a good practice, and certainly not in your DataLoader as you can't guarantee the order in which the save/delete-logic is executed (maybe the TrashcanDao is saved before you delete everything).
To solve this, you should create a proper reactive stream:
trashcanRepository
.deleteAll()
.then(trashcanRepository.save(new TrashcanDao(address1, FillStatus.EMPTY)))
.thenMany(trashcanRepository.findAll())
// Your previous subscribe() shouldn't compile since it should contain List<TrashcanDao>
.subscribe(trashcans -> log.info(trashcans.size()));
I am new to Boot-Spring apparently, I mostly copy some code from youtube on this case. However, after modification, in the end, I got a message like this;
APPLICATION FAILED TO START
Description:
Field postService in com.example.demo.BlogController required a bean of type 'Server.PostService' that could not be found.
Action:
Consider defining a bean of type 'Server.PostService' in your configuration.
.....Any idea how to deal with this situation. Thank you for the support.
1stclass-BlogApplciation-----com.example.demo(package)
2nd-Blog Controller--------same package as BlogApplication
3rdclass-Post---entities
4rthclass-PostRepositories---Repositories
**package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}
}**
**package com.example.demo;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import Server.PostService;
import entities.Post;
import java.util.Date;
#RestController
public class BlogController {
#Autowired
private PostService postService;
#GetMapping(value="/")
public String index() {
return "index";
}
#GetMapping(value="/posts")
public List<Post>posts(){
return postService.getAllPosts();
}
#PostMapping(value="/post")
public void publishPost(#RequestBody Post post) {
if(post.getDatecreation() == null)
post.setDatecreation(new Date());
postService.insert(post);
}
}**
**package entities;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
public class Post {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String title;
private String body;
private Date Datecreation;
public Post() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String gettitle() {
return title;
}
public void settitle(String title) {
this.title= title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Date getDatecreation() {
return Datecreation;
}
public void setDatecreation(Date datecreation) {
this.Datecreation = datecreation;
}
}**
**package Repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import entities.Post;
#Repository
public interface PostRepository extends JpaRepository<Post,Long>{
}**
Your BlogApplication Class, which is the class annotated with #SpringBootApplication is in the package com.example.demo. That means that, by default, Spring is going to launch a Component Scan starting from that package.
The problem is that your class PostService and your interface PostRepository are not in the same package as (or in a sub-package of) com.example.demo, so Spring can't find them and won't automatically create these beans for you.
To correct this issue, move the packages you created inside your root package (com.example.demo).
You can find more information about the use of #SpringBootApplication here.
EDIT:
You are missing PostService class or you have imported incorrect class as Server.PostService.
try to create a service like this one:
#Component
public class PostService {
public List<Post> getAllPosts(){
//your code
}
}
I would like to create a custom annotation in my Spring Boot application which always adds a prefix to my class level RequestMapping path.
My Controller:
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import org.springframework.web.bind.annotation.GetMapping;
#MyApi("/users")
public class UserController {
#GetMapping("/stackoverflow")
public String get() {
return "Best users";
}
}
My custom annotation
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
#RequestMapping(path = "/api")
public #interface MyApi {
#AliasFor(annotation = RequestMapping.class)
String value();
}
GOAL: a mapping like this in the end: /api/users/stackoverflow
Notes:
server.servlet.context-path is not an option because I want to create
several of these
I'm using Spring Boot version 2.0.4
I was not able to find an elegant solution for the issue. However, this worked:
Slightly modified annotation, because altering behavior of value turned out to be more difficult.
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#RestController
#RequestMapping
public #interface MyApi {
#AliasFor(annotation = RequestMapping.class, attribute = "path")
String apiPath();
}
Bean Annotation Processor
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
#Component
public class MyApiProcessor implements BeanPostProcessor {
private static final String ANNOTATIONS = "annotations";
private static final String ANNOTATION_DATA = "annotationData";
public Object postProcessBeforeInitialization(#NonNull final Object bean, String beanName) throws BeansException {
MyApi myApi = bean.getClass().getAnnotation(MyApi.class);
if (myApi != null) {
MyApi alteredMyApi = new MyApi() {
#Override
public Class<? extends Annotation> annotationType() {
return MyApi.class;
}
#Override
public String apiPath() {
return "/api" + myApi.apiPath();
}
};
alterAnnotationOn(bean.getClass(), MyApi.class, alteredMyApi);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(#NonNull Object bean, String beanName) throws BeansException {
return bean;
}
#SuppressWarnings("unchecked")
private static void alterAnnotationOn(Class clazzToLookFor, Class<? extends Annotation> annotationToAlter, Annotation annotationValue) {
try {
// In JDK8 Class has a private method called annotationData().
// We first need to invoke it to obtain a reference to AnnotationData class which is a private class
Method method = Class.class.getDeclaredMethod(ANNOTATION_DATA, null);
method.setAccessible(true);
// Since AnnotationData is a private class we cannot create a direct reference to it. We will have to manage with just Object
Object annotationData = method.invoke(clazzToLookFor);
// We now look for the map called "annotations" within AnnotationData object.
Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
annotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> map = (Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
map.put(annotationToAlter, annotationValue);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Controller:
import com.sagemcom.smartvillage.smartvision.common.MyApi;
import org.springframework.web.bind.annotation.GetMapping;
#MyApi(apiPath = "/users")
public class UserController {
#GetMapping("/stackoverflow")
public String get() {
return "Best users";
}
}
I am trying to implement a custom password match validation in spring boot. But I am getting an error as follows:-
PasswordMatch contains Constraint annotation, but does not contain a message parameter
I am following this link https://www.baeldung.com/registration-with-spring-mvc-and-spring-security for custom validation. The problem is I am getting error as this.
javax.validation.ConstraintDefinitionException: HV000074: com.bikram.booking.validation.PasswordMatch contains Constraint annotation, but does not contain a message parameter.
at org.hibernate.validator.internal.metadata.core.ConstraintHelper.assertMessageParameterExists(ConstraintHelper.java:915)
I have searched solutions on web but couldn't find the decent soultion.
My Modal is
package com.bikram.booking.dto;
import com.bikram.booking.validation.PasswordMatch;
import com.bikram.booking.validation.ValidEmail;
import javax.validation.constraints.*;
#PasswordMatch
public class UserDto {
#NotNull
#Size(min = 6, message = "Password should be more than 6 characters")
#NotEmpty(message = "Please provide a password")
private String password;
#NotNull
#Size(min = 6, message = "Password should be more than 6 characters")
private String confirmPassword;
}
My Interface is
package com.bikram.booking.validation;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({ TYPE, ANNOTATION_TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = PasswordMatchValidator.class)
#Documented
public #interface PasswordMatch {
String messages() default "Sorry, passwords does not match";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And Implementation is
package com.bikram.booking.validation;
import com.bikram.booking.dto.UserDto;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PasswordMatchValidator implements ConstraintValidator<PasswordMatch, Object> {
#Override
public void initialize(PasswordMatch constraintAnnotation) {
}
#Override
public boolean isValid(Object obj, ConstraintValidatorContext constraintValidatorContext) {
UserDto userDto = (UserDto) obj;
return true;
}
}
Any hints will be higly appreciable.
Rename messages() to message() in PasswordMatch :
public #interface PasswordMatch {
String message() default "Sorry, passwords does not match";
....
}
I want to validate two fields of a Request Class in manner that Either one field is valid OR another field is valid.
Eg:
Request Bean
public class CarRequest {
#NotEmpty
private String customerName;
#NotEmpty
private String customerId;
Controller Method
public #ResponseBody CarResponse addCar(
#ModelAttribute #Valid CarRequest request, BindingResult results)
throws RuntimeException, ValidationException {
if (results.hasErrors()) {
LOG.error("error occured while adding the car");
throw new ValidationException(
"Error Occoured while validiating car request");
}
}
Here I want to check that either customerName should be NotEmpty OR customerId should be NotEmpty. then my validation should pass. How can I implement it . Please suggest!!
You need to create custom validator to validate multiple fields.
create a custom annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
#Documented
#Constraint(validatedBy = CarRequestValidator.class)
#Target({ ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface RequestAnnotation {
String message() default "{RequestAnnotation}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
create a custom validator:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class CarRequestValidator implements
ConstraintValidator<RequestAnnotation, CarRequest> {
#Override
public void initialize(RequestAnnotation constraintAnnotation) {
}
#Override
public boolean isValid(CarRequest value, ConstraintValidatorContext context) {
// validation logic goes here
return false;
}
}
Now, annotate your model with custom annotation:
#RequestAnnotation
public class CarRequest {
private String customerName;
private String customerId;
}