How can I make javax validation both of sub class and super class? - java

I'm using Spring framework,
and I faced the inheritance problem when I write Controller logic.
First of all,
this is my Controller code snippet
#PostMapping("/pay/detail")
public ResponseEntity<PayDetail.Response> getPayDetail(
#Valid PayDetail.Request payDetail
) {
... some code
}
and PayDetail class looks like this
public class PayDetail {
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public static class Request extends CommReqForm {
#NotNull(message = "Not null please")
private String work_type;
}
}
and CommReqForm
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CommReqForm {
#NotEmpty(message = "servicecode not empty")
private String servicecode;
#NotEmpty(message = "reqtype not empty")
private String reqtype;
}
I wish that I can validate both of PayDetail.Request and CommReqForm classes but It makes validation just only PayDetail.Request class.
How can I solve this problem?
#Valid cannot validate super class. I want to make both of sub class and super class validation.

Related

Spring abstract class with final fields and inheritance with lombok's #SuperBuilder

I am currently trying to remove some boilerplate code with lombok but have some trouble.
I have an abstract class AbstractParent,
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final field1;
private final field2;
then I have a Child Class like this
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public abstract class Child extends AbstractParent {
And I also have some classes extending the Child class
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
public abstract class ExtendedChild extends Child {
private final field1;
private final field2;
Since lombok can't use super in a constructor, I tried the #SuperBuilder Annotation instead of defining the Constructors manually but can't get the Application to start. Am I missing something completely? Is this even possible with lombok and spring?
The Error is:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.fu.extendedChild required a bean of type 'com.fu.extendedChild$extendedChildBuilder' that could not be found.
Action:
Consider defining a bean of type 'com.fu.extendedChild$extendedChildBuilder' in your configuration.
I was able to reproduce your problem with this code
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final String field1;
private final String field2;
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
abstract class Child extends AbstractParent {
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
class ExtendedChild extends Child {
private final String field1;
private final String field2;
}
What the #SuperBuilder does here on class ExtendedChild is
protected ExtendedChild(ExtendedChildBuilder<?, ?> b) {
super(b);
this.field1 = b.field1;
this.field2 = b.field2;
}
So it says you need an ExtendedChildBuilder instance in order to build an ExtendedChild instance. In order words, you have to have a builder in your spring context to be able to create your object.
This is not a good idea since a builder is stateful and not thread-safe. Furthermore, the builder pattern is here to be able to provide values whenever you need them before constructing your object. Using a builder as a Spring bean denies that advantage.
If this is immutability you want to achieve, then using plain old constructors with the right parameters is way better (and when done right, this is not boilerplate code, this is good design).
Then, Spring injection will be a child's play.
Please do not trade complexity for the sake of writing less code :)

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

Why #Data and #Builder doesnt work together

I have this simple class
public class ErrorDetails {
private String param = null;
private String moreInfo = null;
private String reason = null;
...
}
After refactoring, I added #Data and #Builder, but all the instantiations doesn't work any more
ErrorDetails errorDetails = new ErrorDetails();
'ErrorDetails(java.lang.String, java.lang.String, java.lang.String)'
is not public in
'com.nordea.openbanking.payments.common.ndf.client.model.error.ErrorDetails'.
Cannot be accessed from outside package
If I removed #Builder, then it will work fine,
Why I cannot use #Data and #Builder together?
Lombok's #Builder must have #AllArgsConstructor in order to work
Adding also #AllArgsConstructor should do
Under the hood it build all fields using constructor with all fields
applying #Builder to a class is as if you added #AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the #Builder annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself.
The full config should be :
#Data
#Builder(toBuilder = true)
#AllArgsConstructor
#NoArgsConstructor
class ErrorDetails {
private String param; // no need to initiate with null
private String moreInfo;
private String reason;
}

Lombok builder override default constructor

I was setting the value of recordId from the child classes using the default constructor and was not using lombok #Builder initially. Eventually i decided to use the Builder here, but the problem now is lombok Builder overrides my default constructor internally hence the value is never set.
How can I put any hook too make lombok #Builder use my default constructor?
Parent class:
#Getter
#Setter
public abstract class Record {
private String recordId;
}
Child class:
#Getter
#Setter
#Builder
#ToString
#AllArgsConstructor
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord() {
setRecordId(RECORD_ID); //value of recordId being set
}
}
Lombok's #Builder simply does not use the default constructor. It passes its values to an all-args constructor so that this constructor can fill the new instance with these values. #Builder does not use setters or direct access to the fields to do so. So your default constructor is simply ignored by #Builder.
What you can do is write your own all-args constructor. In it, you set your value for recordId and assign the rest of the fields from the parameters.
I think you should create a constructor in your base class:
#Getter
#Setter
public abstract class Record {
private String recordId;
public Record(String recordId) {
this.recordId = recordId;
}
}
Then use it in the constructor of the inherited class:
#Getter
#Setter
#Builder
public class SRecord extends Record {
private static final String RECORD_ID = "REC001";
private String street;
private String city;
public SRecord(String street, String city) {
super(RECORD_ID);
this.street = street;
this.city = city;
}
}
P.S. If you want to use Lombok Builder with inheritance you can use this technique.

Add validation rules to Pojo.name to object extending Pojo?

I have a simple java pojo which i give to my android users:
#XmlRootElement
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class PostAccount {
#Getter
#Setter
private String email;
#Getter
#Setter
private String pass1;
#Getter
#Setter
private String pass2;
}
This pojo is serialized to json and send to my server. On my server i which to use jersey bean validation:
public NumberResult add(#Valid ValidAccount account) {
But because the Account pojo doesn't have any validation annotations validation doesn't do much.
I can create a second pojo with validation annotations and use that on server side:
public class ValidAccount {
#Getter
#Setter
#NotEmpty
#CheckEmail
private String email;
#Getter
#Setter
#NotBlank
private String pass1;
#Getter
#Setter
#NotBlank
private String pass2;
}
Works perfectly.
But when i now add a field at Account pojo i do have to remember to change ValidAccount pojo. No problem, but when you have a lot of pojo's things get complicated to manage.
Is there a better solution?
For example is it possible to extends the Account pojo and in a way add the validation rules? (please i which to continue using annotations, xml gives me the creeps)

Categories