I am using custom validation in entity class, #Valid annotation on service class not in controller class and custom exception controller(#ControllerAdvice) in Spring Boot.
When I am using #Valid in controller the custom annotation is throwing MethodArgumentNotValidException and I am able to handle it.
The problem comes
when I am using #Valid in service class the custom annotation stopped thowing exception. I want to handle custom annotation in ConstraintViolationException. I am using custom annotation on object level not field level. Please help
I got the solution is is look like following:
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
ValidationErrorReponse onConstraintValidationException(ConstraintViolationException e) {
ValidationErrorReponse error = new ValidationErrorReponse();
Map<String, List<String>> errorMap = new HashMap<>();
List<Violation> violations = new ArrayList<>();
for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
if (!errorMap.containsKey(violation.getPropertyPath().toString())) {
List<String> errorMessages = new ArrayList<>();
if(!violation.getMessage().isEmpty()) {
errorMessages.add(violation.getMessage());
errorMap.put(violation.getPropertyPath().toString(), errorMessages);
}else {
ConstraintDescriptor<?> objEceptions = violation.getConstraintDescriptor();
errorMessages.add((String)objEceptions.getAttributes().get("errormessage"));
String errorField = (String)objEceptions.getAttributes().get("errorField");
errorMap.put(violation.getPropertyPath().toString().concat("."+errorField), errorMessages);
}
} else {
errorMap.get(violation.getPropertyPath().toString()).add(violation.getMessage());
}
}
for (Entry<String, List<String>> entry : errorMap.entrySet()) {
Violation violation = new Violation(entry.getKey(), entry.getValue());
violations.add(violation);
}
error.setViolations(violations);
return error;
}
}
Related
I am writing Junit test case for the following class :
#Component
public class ExpandParam {
/* expand parameter with value "expand" */
#Value("${api.expand.value}")
private String expandParam;
public MultiValueMap<String, String> getExpandQueryParam(String[] expand) {
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
// Creating comma separated format string
StringBuilder builder = new StringBuilder();
for (String value : expand) {
if(!expand[expand.length-1].equals(value)) {
builder.append(value+", ");
}
else {
builder.append(value);
}
}
String expandText = builder.toString();
queryParams.add(expandParam, expandText);
return queryParams;
}
}
The test class is following :
public class ExpandParamTest {
#InjectMocks
#Spy
ExpandParam expandQueryParam;
// #Value("${api.expand.value}")
// private String expandParam;
private String[] expand = {"fees"};
#Before
public void setup() {
ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand");
}
#Test
public void testExpandParam() {
MultiValueMap<String, String> queryParams = expandQueryParam.getExpandQueryParam(expand);
try {
System.out.println(new ObjectMapper().writeValueAsString(queryParams));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
In application. properties files I have set the values :
#expand param
api.expand.value: expand
I am new to this, can any one tell me where I am making the mistake:
Getting the following error:
java.lang.IllegalArgumentException: Either targetObject or targetClass for the field must be specified
at org.springframework.util.Assert.isTrue(Assert.java:121)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:178)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:107)
at org.springframework.test.util.ReflectionTestUtils.setField(ReflectionTestUtils.java:91)
at com.aig.rs.products.orchestrator.api.utils.ExpandParamTest.setup(ExpandParamTest.java:29)
#Value is a spring annotation, it depends on the Spring Context to function. If you want #Value to read the value from your application properties then you need to convert your unit test into a #SpringBootTest. Take a look at this tutorial to understand a bit more about Spring Test.
You're also using ReflectionTestUtils.setField(expandQueryParam, "expandParam", "expand"); which will just set a value to this field, not read it from properties. This exception you're seeing is because expandQueryParam is null, these annotations #Spy and #InjectMocks are Mockito annotations and for them to initialize your object you need to enable mockito annotations, you can do this by adding #ExtendWith(MockitoExtension.class) on top of your class or using MockitoAnnotations.initMocks(this) in setUp method.
I don't think you need mockito to test this class, in my opinion going for a Spring Test would be a better option this way you can also test the reading of the property key.
I am struggling to find a list of jpa-s in my Spring web app by using the PagingAndSortingRepository but i always get some kind of an exception.
(my userRepository extends JpaRepository which extends PagingAndSortingRepository)
This is my code :
#RestController
#RequestMapping(value = "")
public class UsersController {
...
#RequestMapping(method=RequestMethod.GET, value = "/view-u", produces = MediaType.APPLICATION_JSON_VALUE)
#PreAuthorize("hasRole(T(com.myapp.util.Um.Privileges).CAN_USER_READ)")
public #ResponseBody List<JsonUser> getUsersPaginated(){
List<JsonUser> result = new ArrayList<JsonUser>();
result.addAll(dashboardServiceImpl.findUsersPaginatedAndSorted());
return result;
}
...
}
and service :
#Service
public class DasboardServiceImpl implements DashboardService /*My interface*/{
...
#Override
public List<JsonUser> findUsersPaginatedAndSorted(){
List<JsonUser> modelList = new ArrayList<JsonUser>();
final List<UserJPA> jpaList = userRepository.findAll(new PageRequest(0, 10, Sort.Direction.ASC, "username")).getContent();
for(final UserJPA jpa : jpaList){
modelList.add(new JsonUser(jpa));
}
return modelList;
}
}
This doesn't work. It throws the exception :
Unknown name value [] for enum class [com.health.common.enums.Gender];
nested exception is java.lang.IllegalArgumentException: Unknown name
value [] for enum class [com.health.common.enums.Gender]
I attest that the Gender enum works perfectly in every other database communication.
I then tried doing this to the service method :
#Override
public List<JsonUser> findUsersPaginatedAndSorted(){
List<JsonUser> modelList = new ArrayList<JsonUser>();
try{
// First call -> enum exception
userRepository.findAll(new PageRequest(0, 10, Sort.Direction.ASC, "username"));
}catch(Exception e){
System.out.println("\n\t"+e.getMessage()+"\n");
}
// Second call - collection exception
final List<UserJPA> jpaList = userRepository.findAll(new PageRequest(0, 10, Sort.Direction.ASC, "username")).getContent();
for(final UserJPA jpa : jpaList){
modelList.add(new JsonUser(jpa));
}
return modelList;
}
And this time, i get a different exception on the second findAll call :
org.springframework.orm.jpa.JpaSystemException: collection is not
associated with any session; nested exception is
org.hibernate.HibernateException: collection is not associated with
any session
After a lot of digging and frustration, i found the only solution which works for me :
I add #Transactional(readOnly=true) to the service method and catch different exceptions in both service and controller :
controller :
#RestController
#RequestMapping(value = "")
public class UsersController {
...
#RequestMapping(method=RequestMethod.GET, value = "/view-u", produces = MediaType.APPLICATION_JSON_VALUE)
#PreAuthorize("hasRole(T(com.health.common.util.Um.Privileges).CAN_USER_READ)")
public #ResponseBody List<JsonUser> getUsersPaginated(){
List<JsonUser> result = new ArrayList<JsonUser>();
// i have no idea why
try{
// when called the first time, it throws "Transaction marked as rollbackOnly"
result.addAll(dashboardServiceImpl.findUsersPaginatedAndSorted());
}catch(Exception e){
// and when called the second time, it works
result.addAll(dashboardServiceImpl.findUsersPaginatedAndSorted());
}
return result;
}
...
}
( Notice the exception i'm getting on the first service method call is :
TransactionSystemException : Could not commit JPA transaction; nested
exception is javax.persistence.RollbackException: Transaction marked
as rollbackOnly
)
And service :
#Service
public class DasboardServiceImpl implements DashboardService{
...
#Override
#Transactional(readOnly=true)
public List<JsonUser> findUsersPaginatedAndSorted(){
List<JsonUser> modelList = new ArrayList<JsonUser>();
// I have no idea why
try{
// when called the first time it throws (GenderEnum) exception
userRepository.findAll(new PageRequest(0, 10, Sort.Direction.ASC, "username"));
}catch(Exception e){
System.out.println("\n\t"+e.getMessage()+"\n");
}
// and when called the second time, it works
final List<UserJPA> jpaList = userRepository.findAll(new PageRequest(0, 10, Sort.Direction.ASC, "username")).getContent();
for(final UserJPA jpa : jpaList){
modelList.add(new JsonUser(jpa));
}
return modelList;
}
}
As i said, this works for me and i get a proper list of users, but this solution is far from elegant. Notice that i am doing a double call of both service method and of repository method. :(
How can i fix my code so i don't have to do these double calls ?
Why doesn't my original solution work ? Why does findAll(Paginated) requires #Transactional when it is a query that doesn't alter the database ?
Sorry for the long post and thank you in advance.
I need to validate a POJO, without using the annotation #Valid as paramether of a method.
I'm stuck at this point:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Menu>> constraintViolations=validator.validate(menu);
//Menu is my Pojo
if (!constraintViolations.isEmpty()) {
Iterator itr = constraintViolations.iterator();
while(itr.hasNext()){
Object o = itr.next();
}
}
It seems to work, since inside "itr" I have stuff like this::
oConstraintViolationImpl{interpolatedMessage='size must be between 3 and 50', propertyPath=titolo, rootBeanClass=class com.springgestioneerrori.model.Menu, messageTemplate='{javax.validation.constraints.Size.message}'}
Now, my question is: how can I add "itr" values to BindingResult??? Probably I should cast something...somehow...
Thank you
#Test
public void testBindingResult(BindingResult result,Set<ConstraintViolation<String>> violations){
for ( ConstraintViolation<String> constraintViolation : violations) {
ObjectError error=new ObjectError("object",constraintViolation.getMessage());
result.addError(error);
}
}
you should create object of type ObjectError here is a spring doc for the class and you should add it to binding result.
I'm doing a lot of our validation with Hibernate and Spring Annotations like so:
public class Account {
#NotEmpty(groups = {Step1.class, Step2.class})
private String name;
#NotNull(groups = {Step2.class})
private Long accountNumber;
public interface Step1{}
public interface Step2{}
}
And then in the controller it's called in the arguments:
public String saveAccount(#ModelAttribute #Validated({Account.Step1.class}) Account account, BindingResult result) {
//some more code and stuff here
return "";
}
But I would like to decide the group used based on some logic in the controller method. Is there a way to call validation manually? Something like result = account.validate(Account.Step1.class)?
I am aware of creating your own Validator class, but that's something I want to avoid, I would prefer to just use the annotations on the class variables themselves.
Spring provides LocalValidatorFactoryBean, which implements the Spring SmartValidator interface as well as the Java Bean Validation Validator interface.
// org.springframework.validation.SmartValidator - implemented by LocalValidatorFactoryBean
#Autowired
SmartValidator validator;
public String saveAccount(#ModelAttribute Account account, BindingResult result) {
// ... custom logic
validator.validate(account, result, Account.Step1.class);
if (result.hasErrors()) {
// ... on binding or validation errors
} else {
// ... on no errors
}
return "";
}
Here is a code sample from JSR 303 spec
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Driver driver = new Driver();
driver.setAge(16);
Car porsche = new Car();
driver.setCar(porsche);
Set<ConstraintViolation<Driver>> violations = validator.validate( driver );
So yes, you can just get a validator instance from the validator factory and run the validation yourself, then check to see if there are violations or not. You can see in the javadoc for Validator that it will also accept an array of groups to validate against.
Obviously this uses JSR-303 validation directly instead of going through Spring validation, but I believe spring validation annotations will use JSR-303 if it's found in the classpath
If you have everything correctly configured, you can do this:
import javax.validation.Validator;
#Autowired
Validator validator;
Then you can use it to validate you object:
var errors = validator.validate(obj);
This link gives pretty good examples of using validations in Spring apps.
https://reflectoring.io/bean-validation-with-spring-boot/
I have found an example to run the validation programmitically in this article.
class MyValidatingService {
void validatePerson(Person person) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Person>> violations = validator.validate(person);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
It throws 500 status, so it is recommended to handle it with custom exception handler.
#ControllerAdvice(annotations = RestController.class)
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<CustomErrorResponse> constraintViolationException(HttpServletResponse response, Exception ex) throws IOException {
CustomErrorResponse errorResponse = new CustomErrorResponse();
errorResponse.setTimestamp(LocalDateTime.now());
errorResponse.setStatus(HttpStatus.BAD_REQUEST.value());
errorResponse.setError(HttpStatus.BAD_REQUEST.getReasonPhrase());
errorResponse.setMessage(ex.getMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
Second example is from https://www.mkyong.com/spring-boot/spring-rest-error-handling-example/
Update:
Using validation is persistence layer is not recommended:
https://twitter.com/odrotbohm/status/1055015506326052865
Adding to answered by #digitaljoel, you can throw the ConstraintViolationException once you got the set of violations.
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<NotionalProviderPaymentDTO>> violations = validator.validate( notionalProviderPaymentDTO );
if(!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
You can create your own exception mapper which will handle ConstraintViolationException and send the errors messages to the client.
And also:
#Autowired
#Qualifier("mvcValidator")
Validator validator;
...
violations = validator.validate(account);
import javax.validation.Validator;
import javax.validation.ConstraintViolation;
public class{
#Autowired
private Validator validator;
.
.
public void validateEmployee(Employee employee){
Set<ConstraintViolation<Employee>> violations = validator.validate(employee);
if(!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
Here, 'Employee' is a pojo class and 'employee' is it's object
Is there a way to get a list of all Controllers, which were annotated with #Controller? I would like to use them like:
#Autowired
public void addAll(List<Controller> controllers) throws Exception {
for (Controller controller : controllers) {
...
}
}
Thanks!
getBeansWithAnnotation()
If you have annotated them with controller ... :
#Autowired
private ListableBeanFactory listableBeanFactory;
then
Map<String, Object> controllers;
controllers = listableBeanFactory.getBeansWithAnnotation(Controller.class);
You can probably also use the fact that <context-component:scan /> and BeanFactory does most of this for you. #NimChimpsky gave an excellent example.
You could scan the classpath, using either
AnnotationUtils.html#findAnnotationDeclaringClass
or manually using something like the following sample code
public List<Class> scanForComponents() {
List<Class> components = new LinkedList<Class>();
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(Component.class));
for (String componentBasePacke : componentBasePackages) {
for (BeanDefinition bd : scanner.findCandidateComponents(componentBasePacke)) {
try {
components.add(Class.forName(bd.getBeanClassName()));
} catch (ClassNotFoundException ex) {
}
}
}
return components;
}