Javax validation on nested objects - not working - java

In my Spring Boot project I have two DTO's which I'm trying to validate, LocationDto and BuildingDto. The LocationDto has a nested object of type BuildingDto.
These are my DTO's:
LocationDto
public class LocationDto {
#NotNull(groups = { Existing.class })
#Null(groups = { New.class })
#Getter
#Setter
private Integer id;
#NotNull(groups = { New.class, Existing.class })
#Getter
#Setter
private String name;
#NotNull(groups = { New.class, Existing.class, LocationGroup.class })
#Getter
#Setter
private BuildingDto building;
#NotNull(groups = { Existing.class })
#Getter
#Setter
private Integer lockVersion;
}
BuildingDto
public class BuildingDto {
#NotNull(groups = { Existing.class, LocationGroup.class })
#Null(groups = { New.class })
#Getter
#Setter
private Integer id;
#NotNull(groups = { New.class, Existing.class })
#Getter
#Setter
private String name;
#NotNull(groups = { Existing.class })
#Getter
#Setter
private List<LocationDto> locations;
#NotNull(groups = { Existing.class })
#Getter
#Setter
private Integer lockVersion;
}
Currently, I can validate in my LocationDto that the properties name and building are not null, but I can't validate the presence of the property id which is inside building.
If I use the #Valid annotation on the building property, it would validate all of its fields, but for this case I only want to validate its id.
How could that be done using javax validation?
This is my controller:
#PostMapping
public LocationDto createLocation(#Validated({ New.class, LocationGroup.class }) #RequestBody LocationDto location) {
// save entity here...
}
This is a correct request body: (should not throw validation errors)
{
"name": "Room 44",
"building": {
"id": 1
}
}
This is an incorrect request body: (must throw validation errors because the building id is missing)
{
"name": "Room 44",
"building": { }
}

Just try adding #valid to collection. it would be working as per reference hibernate
#Getter
#Setter
#Valid
#NotNull(groups = { Existing.class })
private List<LocationDto> locations;

#Valid annotation must be added to cascade class attributes.
LocationDTO.class
public class LocationDto {
#Valid
private BuildingDto building;
.........
}

Use #ConvertGroup from Bean Validation 1.1 (JSR-349).
Introduce a new validation group say Pk.class. Add it to groups of BuildingDto:
public class BuildingDto {
#NotNull(groups = {Pk.class, Existing.class, LocationGroup.class})
// Other constraints
private Integer id;
//
}
And then in LocationDto cascade like following:
#Valid
#ConvertGroup.List( {
#ConvertGroup(from=New.class, to=Pk.class),
#ConvertGroup(from=LocationGroup.class, to=Pk.class)
} )
// Other constraints
private BuildingDto building;
Further Reading:
5.5. Group conversion from Hibernate Validator reference.

Related

#Valid annotation is not working and giving NotReadablePropertyException

I have request class for a patch API in the below format
#Schema
#Data
#EqualsAndHashCode(callSuper = true)
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class PricingUsageTemplatePatchInput extends BaseRequest {
#Schema(description = "From node of the Template")
private JsonNullable<#Valid VertexNode> from;
#Schema(description = "To node of the Template")
private JsonNullable<#Valid VertexNode> to;
#NotNull
#Schema(description = "Current version of the template for which update is being made.")
private Long version;
}
VertexNode is as below
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class VertexNode {
#NotNull
#Valid
private Selectors selectors;
#NotNull
#Valid
private Cardinality cardinality;
}
And in the controller layer is as below
#PatchMapping("/{key}")
#Operation(description = "API to update the template")
public PricingUsageTemplateResponse update(#PathVariable #IsUUID final String key,
#Valid #RequestBody #NotNull #Parameter(description = "PricingUsageTemplatePatchInput", required = true)
PricingUsageTemplatePatchInput pricingUsagePatchInput) {
var request = PricingUsageTemplateUpdateRequest.builder()
.key(key)
.pricingUsageTemplatePatchInput(pricingUsageTemplatePatchInput)
.build();
return (PricingUsageTemplateResponse) actionRegistry.get(request).invoke(request);
}
When I am sending selector as null from the postman for the above api , the valid annotation is not able to send valid not null validation error instead I am getting 5xx error with below reason
org.springframework.beans.NotReadablePropertyException: Invalid property 'to.selectors' of bean class [domain.pricing_usage.dto.request.PricingUsageTemplatePatchInput]: Bean property 'to.selectors' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
Can anyone help why #Valid is not working as expected

Getting DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted for multilevel/complex object

I have a complex object Test in the entity class Item.
#AllArgsConstructor
#Getter
public enum TestStatus {
TO_RUN("To Run"),
RUNNING("Running"),
PASSED("Passed"),
FAILED("Failed");
public static TestStatus fromValue(String value) {
//...implementation
}
private final String value;
}
#Data
#ToString
#Accessors(chain = true)
#DynamoDBFlattened(attributes = {
#DynamoDBAttribute(attributeName = "test.task.id", mappedBy = "id"),
#DynamoDBAttribute(attributeName = "test.task.status", mappedBy = "status")
})
public class TestTask {
private String id;
#DynamoDBTypeConvertedEnum
private TestStatus status;
}
#Data
#ToString
#Accessors(chain = true)
#DynamoDBFlattened(attributes = {
#DynamoDBAttribute(attributeName = "test.suite.name", mappedBy = "name"),
#DynamoDBAttribute(attributeName = "test.suite.version", mappedBy = "version")
})
public class TestSuite {
private String name;
private String version;
}
#Data
#ToString
#Accessors(chain = true)
public class Test {
private TestSuite suite;
private TestTask task;
}
#Data
#ToString
#Accessors(chain = true)
#DynamoDBTable(tableName = "com.example.item")
public class Item {
private String name;
private Test test; // This is a complex object as structure given above.
}
On the call of dynamoDBMapper.save(item); getting exception.
#Repository
#RequiredArgsConstructor
public class DynamoDBItemRepository implements ItemRepository {
//...
#Override
public Item save(Item item) {
dynamoDBMapper.save(item); // Getting DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
return item;
}
//...
}
I am getting the exception
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$Rules$NotSupported.set(StandardModelFactories.java:664) ~[aws-java-sdk-dynamodb-1.11.578.jar:?]
What am I missing? Please help!
There are two problems in the code.
I tried to reproduce the error, but found the first problem: no hash key specified.
so I used Item.name as the hash key in order to go further on the test.
The second problem matched your description
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: not supported; requires #DynamoDBTyped or #DynamoDBTypeConverted
Found out that you missed an annotation #DynamoDBDocument, which should be added to the class Test since it is a nested type:
...
#DynamoDBDocument
...
public class Test {
see document here
I suggest to migrate to AWS SDK for Java 2.0 where you can use complex objects: doc

Spring boot post api - org.postgresql.util.PSQLException: ERROR: null value in column "password" violates not-null constraint

I have created a spring boot application. And in the application I'm working with API's. I have an POST API for creating an user, but every time I use the api, i will get the following error
Spring boot post api - org.postgresql.util.PSQLException: ERROR: null value in column "password" violates not-null constraint
Im using hibernate and PostgreSQL as database.
Even if I give de data values, i will get this error. I searched over the internet, but I cant find a solution for this problem
This is the code that i user:
Users.java:
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder(toBuilder = true)
#ToString
#Table
public class Users {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_id;
#Column(unique = true, nullable = false)
private String username;
#Column(nullable = false)
private String password;
}
UsersRepository.java
#Repository
public interface UsersRepository extends JpaRepository<Users, Integer> {}
UsersService
#Service
#RequiredArgsConstructor
#Transactional
public class UsersService {
private final UsersRepository usersRepository;
public Users createUser(Users newUsers) {
return this.usersRepository.save(newUsers);
}
}
UsersDto
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class UsersDto {
private int user_id;
private String username;
private String password;
}
UsersMapper
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UsersMapper {
UsersDto toDto(Users users);
Users toModel(UsersDto usersDto);
}
UsersController
#RestController
#CrossOrigin(origins = "http://localhost:4200")
#RequiredArgsConstructor
#RequestMapping("/users")
#Validated
public class UsersController {
private final UsersService usersService;
private final UsersMapper usersMapper;
#PostMapping()
#ApiOperation(value = "Create new user", httpMethod = "POST", code = 201)
#ResponseBody
public ResponseEntity<UsersDto> createUser(#RequestBody UsersDto usersDto) throws Exception {
Users users = this.usersMapper.toModel(usersDto);
Users createUsers = this.usersService.createUser(users);
return ResponseEntity.ok(this.usersMapper.toDto(createUsers));
}
}
I hope someone can help me with this problem
The error says that the password is null, what is not allowed. So it's probably not provided on the request send to your API. You could add validation constraints to the endpoint, like
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class UsersDto {
private int user_id;
#NotNull
private String username;
#NotNull
private String password;
}
One more note, storing password as plain text is not advised , it's a major security issue. You should create and use a hash instead, check out Spring Security for this.
PS - check out bootify.io - here you can create your REST API together with the validation constraints.

Java Spring Data JPA and REST API: exclude fields of nested JSON object

Currently I'm using Spring Data JPA 2.2.1 with a Spring Boot Web 2.2.1 for a REST API service.
A getter call to /categories returns the following JSON, which in fact is the desired result:
[
{
"category1": "A",
"category2": "B",
"subcategories": []
},
{
"category1": "A",
"category2": "B",
"subcategories": [{
"field1": "A",
"field2": "B",
"field3": "C",
"field4": "D"
}]
},
.........
.........
]
Entities:
#Entity
#Table(name = "category")
#Getter #Setter
public class Category {
#JsonIgnore
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String category1;
private String category2;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Subcategory> subcategories;
}
#Entity
#Table(name = "subcategory")
#Getter #Setter
public class Subcategory {
#JsonIgnore
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JsonIgnore
private int parent;
private String field1;
private String field2;
private String field3;
private String field4;
}
Controller:
#RestController
public class MyController {
private final DataService dataService;
#Autowired
public MyController(DataService dataService) {
this.dataService = dataService;
}
#GetMapping("/categories")
public List<Category> getAllCategories() {
return dataService.getAllCategories();
}
Repository
public interface MyRepository extends JpaRepository<Category, Long> {
List<MyRepository> findAll();
}
DataService
#Component
public class DataService {
private MyRepository myRepository;
#Autowired
public DataService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public List<Category> getAllCategories() { return myRepository.findAll(); }
I now want to add a new call to my API, /limitedCategories, which does not return all of the fields. For example, "category2" of the parent entity, and "field4" of the nested entity shall be excluded.
Problem:
I don't manage to manually select the desired fields within my JPA Repository, and also wouldn't know how to deal with the nested object.
The simple idea to just use findAll and exclude those fields using #JsonIgnore is not possible, as my first request still needs those fields.
When using #Query annotation within my repository to fetch only the desired fields, I cannot find a way to fetch the nested fields for my JSON.
Thanks in advance.
I'm using Spring Data JPA 2.2.1 with a Spring Boot Web 2.2.1.
If you are using Jackson API, I recommend you to use #JsonView feature.
For example
class Person {
// it will be add always
Integer id;
// it will be add only for PutFirstName case
#JsonView(Views.PutFirstName.class)
String firstName;
// it will be add only for PutLastName case
#JsonView(Views.PutLastName.class)
String lastName;
}
class Views {
static class PutFirstName{}
static class PutLastNastName{}
}
// Controller
#JsonView(Views.PutFirstName.class)
#GetMapping
Person getFirstName(){
// result : {id,firstName}
}
#JsonView(Views.PutLastName.class)
#GetMapping
Person getLastName(){
// result : {id, lastName}
}

graphql-spqr can not query the parent class field

I tried to implement entity classes using polymorphism.
This is my BaseEntity
#Getter
#Setter
#Accessors(chain = true)
#MappedSuperclass
#NoArgsConstructor
#AllArgsConstructor
#EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Size(max = 55, message = "name length more then 55")
private String name;
#Size(max = 255, message = "remark length more than 255")
private String remark;
}
And my entity
#Data
#NoArgsConstructor
#Table(name = "sys_user")
#Entity(name = "sys_user")
#Accessors(chain = true)
#ToString(callSuper = true)
#EqualsAndHashCode(callSuper = true)
public class SysUser extends BaseEntity implements Serializable {
#NonNull
private String username;
#NonNull
private String password;
}
In my controller
#Controller
#GraphQLApi
#RequiredArgsConstructor
public class SysUserController implements BaseController {
private final SysUserRepository sysUserRepository;
#GraphQLQuery
public List<SysUser> sysUsers() {
return sysUserRepository.findAll();
}
}
My GraphQL Config
#Configuration
#RequiredArgsConstructor
public class GraphQLConfig {
private final #NotNull List<BaseController> controllerLists;
#Bean
public GraphQLSchema graphqlSchema() {
GraphQLSchemaGenerator generator = new GraphQLSchemaGenerator();
generator.withOperationsFromSingletons(controllerLists.toArray());
return generator.generate();
}
}
Now, I try to get
{
sysUsers {
username
}
}
The result is right
{
"data": {
"sysUsers": [
{
"username": "Hello"
}
]
}
}
But I try to get the parent class field:
{
sysUsers {
name
}
}
I will get a error
{
"errors": [
{
"message": "Validation error of type FieldUndefined: Field 'name' in type 'SysUser' is undefined # 'sysUsers/name'",
"locations": [
{
"line": 3,
"column": 5
}
]
}
]
}
I use io.leangen.graphql:graphql-spqr-spring-boot-starter:0.0.4
How to resolve this question?
Thanks!
Inherited fields will only be exposed if they're within the configured packages. This way, you don't accidentally expose framework fields, JDK fields (like hashCode) etc. If no base packages are configured, SPQR will stay within the package the directly exposed class is.
To configure the base packages, add something like:
graphql.spqr.base-packages=your.root.package,your.other.root.package
to your application.properties file.
Note: These rules will get relaxed in the next release of SPQR, so that all non-JDK fields are exposed by default, as the current behavior seems to confuse too many people.
I'd recommend you to add auto-generation of classes based on the types defined in your graphql schema.
It will provide you more clarity on what is exposed to the user and avoid such errors in future.
Here are the plugins:
Gradle plugin: graphql-java-codegen-gradle-plugin
Maven plugin: grapqhl-java-codegen-maven-plugin

Categories