I'm using PMD v3.12 in my current project.
I have a POJO with some properties:
public class Person {
private String name;
private String surname;
private String state;
private String zip;
private String town;
private Integer id;
private LocalDate dateOfBirth;
private String locationOfBirth;
// GETTERS & SETTERS, HASHCODE, TOSTRING
.
.
.
}
I added an inner Builder class for this Pojo:
public class Person {
private String name;
private String surname;
private String state;
private String zip;
private String town;
private Integer id;
private LocalDate dateOfBirth;
private String locationOfBirth;
// GETTERS & SETTERS, HASHCODE, TOSTRING
...
public static Builder builder() {
return new Builder();
}
public static class Builder {
private final Person person;
public Builder() {
this.person = new Person();
}
public Builder name(String name) {
this.person.setName(name);
return this;
}
// Other Methods to build an instance
public Person build() {
return this.person;
}
}
}
Now when PMD is executed, it tells me that the class Person violates TooManyMethods and should be refactored, pointing to the line number, where the inner class begins.
Since i don't want to refactor my Builder, i tried adding an exclusion to the exclude-pmd.properties file, but it doesn't work:
org.my.path.to.my.file.Person=TooManyMethods
I also tried:
org.my.path.to.my.file.Person.Builder=TooManyMethods
org.my.path.to.my.file.Builder=TooManyMethods
and i tired adding #SuppressWarnings("PMD") on both, the parent class and the inner class, but nothing worked.
I am aware that lombok exists, but in my current company it's not allowed, also changing the settings of PMD to allow more Methods to a class is not possible.
Any help is appreciated, thanks in advance!
Related
I have an input object as
class Person {
private String name;
private String email;
private String phone;
private Address address;
public static class Address {
private String city;
private String pincode;
private String street;
private AddrDetails details;
public static class AddrDetails {
private String state;
private String country;
}
}
}
I am using vavr Validations to validate the input
public static Validation<Seq<ConstraintViolation>, PersonDetailsModel> validatePerson(PersonDetailsRequest request) {
Validation
.combine(
validateName("name", request.getName()),
validateEmail("email", request.getEmail()),
validatePhone("phone", request.getPhone()),
validateAddress(request.getAddress())
).ap((name, email, phone, address) -> new PersonDetailsModel(name, email, phone, address);
}
public static Validation<Seq<ConstraintViolation>, Person.Address> validateAddress(
Person.Address request) {
return Validation
.combine(..
).ap((..) -> new Person.Address(..);
}
In the second function, it returns Seq of ConstraintViolation while validatePerson expects only ConstraintViolation which is why it is failing although I have to add one more level of nesting of validations for AddrDetails. How to handle nested objects validations with this approach.
I am not sure about how shall I go ahead?
In our project we call .mapError(Util::flattenErrors) after .ap. I have the feeling that there is a better way, but this at least solves the nesting.
The method in the Util class looks like this :
public static Seq<ConstraintViolation> flattenErrors(final Seq<Seq<ConstraintViolation>> nested) {
return nested
.flatMap(Function.identity())
.distinct(); // duplicate removal
}
In my spring boot project, I noticed a strange Jackson behavior. I searched over internet, found out what to do, but haven't found out why.
UserDto:
#Setter
#Getter
#AllArgsConstructor
public class UserDto {
private String username;
private String email;
private String password;
private String name;
private String surname;
private UserStatus status;
private byte[] avatar;
private ZonedDateTime created_at;
}
Adding a new user works just fine.
TagDto:
#Setter
#Getter
#AllArgsConstructor
public class TagDto {
private String tag;
}
Trying to add a new tag ends with an error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of TagDto (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
The solution to the problem was to add zero-arg constructor to the TagDto class.
Why does Jackson require no-arg constructor for deserialization in TagDto, while working just fine with UserDto?
Used same method for adding both.
My Tag and User entities are both annotated with
#Entity
#Setter
#Getter
#NoArgsConstructor
and have all args constructors:
#Entity
#Setter
#Getter
#NoArgsConstructor
public class User extends AbstractModel {
private String username;
private String password;
private String email;
private String name;
private String surname;
private UserStatus status;
#Lob
private byte[] avatar;
#Setter(AccessLevel.NONE)
private ZonedDateTime created_at;
public User(final String username, final String password, final String email, final String name, final String surname) {
this.username = username;
this.password = password;
this.email = email;
this.name = name;
this.surname = surname;
this.created_at = ZonedDateTime.now();
}
}
#Entity
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class Tag extends AbstractModel {
private String tag;
}
#MappedSuperclass
#Getter
public abstract class AbstractModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
}
Entity generation:
#PostMapping(path = "/add")
public ResponseEntity<String> add(#Valid #RequestBody final D dto) {
this.abstractModelService.add(dto);
return new ResponseEntity<>("Success", HttpStatus.CREATED);
}
public void add(final D dto) {
//CRUD repository save method
this.modelRepositoryInterface.save(this.getModelFromDto(dto));
}
#Override
protected Tag getModelFromDto(final TagDto tagDto) {
return new Tag(tagDto.getTag());
}
#Override
protected User getModelFromDto(final UserDto userDto) {
return new User(userDto.getUsername(), userDto.getPassword(), userDto.getEmail(), userDto.getName(), userDto.getSurname());
}
Error occurs when parsing JSON
{"tag":"example"}
sent via postman localhost:8081/tag/add, returns
{
"timestamp": "2020-09-26T18:50:39.974+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/tag/add"
}
I am using Lombok v1.18.12 and Spring boot 2.3.3.RELEASE with Jackson v2.11.2.
TL;DR: Solution is at the end.
Jackson supports multiple ways of creating POJOs. The following lists the most common ways, but it likely not a complete list:
Create instance using no-arg constructor, then call setter methods to assign property values.
public class Foo {
private int id;
public int getId() { return this.id; }
#JsonProperty
public void setId(int id) { this.id = id; }
}
Specifying #JsonProperty is optional, but can be used to fine-tune the mappings, together with annotations like #JsonIgnore, #JsonAnyGetter, ...
Create instance using constructor with arguments.
public class Foo {
private int id;
#JsonCreator
public Foo(#JsonProperty("id") int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Specifying #JsonCreator for the constructor is optional, but I believe it is required if there is more than one constructor. Specifying #JsonProperty for the parameters is optional, but is required for naming the properties if the parameter names are not included in the class file (-parameters compiler option).
The parameters imply that the properties are required. Optional properties can be set using setter methods.
Create instance using factory method.
public class Foo {
private int id;
#JsonCreator
public static Foo create(#JsonProperty("id") int id) {
return new Foo(id);
}
private Foo(int id) {
this.id = id;
}
public int getId() {
return this.id;
}
}
Create instance from text value using String constructor.
public class Foo {
private int id;
#JsonCreator
public Foo(String str) {
this.id = Integer.parseInt(id);
}
public int getId() {
return this.id;
}
#JsonValue
public String asJsonValue() {
return Integer.toString(this.id);
}
}
This is useful when a the POJO has a simply text representation, e.g. a LocalDate is a POJO with 3 properties (year, month, dayOfMonth), but is generally best serialized as a single string (yyyy-MM-dd format). #JsonValue identifies the method to be used during serialization, and #JsonCreator identifies the constructor/factory-method to be used during deserialization.
Note: This can also be used for single-value construction using JSON values other than String, but that is very rare.
Ok, that was the background information. What is happening for the examples in the question, it that UserDto works because there is only one constructor (so #JsonCreator is not needed), and many arguments (so #JsonProperty is not needed).
However, for TagDto there is only a single-argument constructor without any annotations, so Jackson classifies that constructor as a type #4 (from my list above), not a type #2.
Which means that it is expecting the POJO to be a value-class, where the JSON for the enclosing object would be { ..., "tag": "value", ... }, not { ..., "tag": {"tag": "example"}, ... }.
To resolve the issue, you need to tell Jackson that the constructor is a property initializing constructor (#2), not a value-type constructor (#4), by specifying #JsonProperty on the constructor argument.
This means that you cannot have Lombok create the constructor for you:
#Setter
#Getter
public class TagDto {
private String tag;
public TagDto(#JsonProperty("tag") String tag) {
this.tag = tag;
}
}
Gotta question regarding mapStruct. I have case where I extend class from base entity and not sure how to map it. Here is my case.
BaseEntity:
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "id")
private Long id;
}
BaseDto:
public class BaseDto {
private Long id;
}
UserEntity:
public class User extends BaseEntity {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
UserDto:
public class UserDto extends BaseDto {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
And mapper is like this:
#Mapper(uses = {BaseMapper.class})
public interface UserMapper {
User userDtoToUser(UserDto userDto);
UserDto userToUserDto(User user);
}
BaseMapper:
#Mapper
public interface BaseMapper {
BaseEntity dtoToEntity(BaseDto baseDto);
BaseDto entityToDto(BaseEntity baseEntity);
}
Problem is that I don't get ID property mapped.
Thank you for your time.
EDIT:
There is no error shown, in mapper implementation (generated code) there is no mapping for that ID:
#Override
public User userDtoToUser(UserDto userDto) {
if ( userDto == null ) {
return null;
}
UserBuilder user = User.builder();
user.name( userDto.getName() );
user.lastName( userDto.getLastName() );
user.username( userDto.getUsername() );
user.password( userDto.getPassword() );
user.profilePicturePath( userDto.getProfilePicturePath() );
return user.build();
}
I'm guessing (as you have not put buider code) the problem is that your builder class does not include parent class field. MapStruct makes some assumption while generating code for mapper. From documentation -
The default implementation of the BuilderProvider assumes the
following:
The type has a parameterless public static builder creation method
that returns a builder. So for example Person has a public static
method that returns PersonBuilder.
The builder type has a parameterless public method (build method)
that returns the type being build In our example PersonBuilder has a
method returning Person.
In case there are multiple build methods, MapStruct will look for a
method called build, if such method exists then this would be used,
otherwise a compilation error would be created.
If you are using Lombok, you can solve this by using #SuperBuilder as -
#SuperBuilder
#Getter
#ToString
public class UserDto extends BaseDto {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
#Getter
#SuperBuilder
class BaseDto {
private Long id;
}
#SuperBuilder
#Getter
#ToString
public class User extends BaseEntity {
private String name;
private String lastName;
private String username;
private String password;
private String profilePicturePath;
}
#Setter
#Getter
#SuperBuilder
class BaseEntity {
private Long id;
}
And generated could looks like -
#Override
public User userDtoToUser(UserDto userDto) {
if ( userDto == null ) {
return null;
}
UserBuilder<?, ?> user = User.builder();
user.id( userDto.getId() );
user.name( userDto.getName() );
user.lastName( userDto.getLastName() );
user.username( userDto.getUsername() );
user.password( userDto.getPassword() );
user.profilePicturePath( userDto.getProfilePicturePath() );
return user.build();
}
I have entity class
public Class StudentEntity{
private int id;
private String name;
private AddressEntity address;
private ProfileEntity profile;
//getter setter
}
public Class StudentDTO{
private int id;
private String name;
private AddressDTO address;
private ProfileDTO profile;
//getter setter
}
when I use BeanUtils.copyProperties(); (from spring/apache common) it copies the id and name alone. How to copy the address and profile also?
If custom util has to be written, could you please share the snippet?
BeanUtils, cloning OR serialization would not work here as the inner data types are different. I would suggest you to set the fields of StudentDTO manually. You could use conversion constructor for AddressDTO and ProfileDTO. Copy constructor is the legal name, but since we are converting type also, better name would be a conversion constructor instead.
An example of conversion constructor in JDK is ArrayList(Collection<? extends E> c) , i.e. https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#ArrayList-java.util.Collection- which generates an ArrayList from any Collection object and copy all items from Collection object to newly created ArrayList object.
Example:
StudentEntity studentEntityObj = new StudentEntity();
studentEntityObj.setId(1);
studentEntityObj.setName("myStudent");
AddressEntity addressEntityObj = new AddressEntity();
addressEntityObj.setCity("myCity");
studentEntityObj.setAddress(addressEntityObj);
// All above lines would be taken care of already (i.e. data is filled from DB)
StudentDTO studentDTOObj = new StudentDTO();
// Call conversion constructor
AddressDTO addressDtoObj = new AddressDTO(addressEntityObj);
studentDTOObj.setAddress(addressDtoObj);
studentDTOObj.setId(studentEntityObj.getId());
studentDTOObj.setName(studentEntityObj.getName());
System.out.println(studentDTOObj.toString());
where AddressDTO (OR ProfileDTO for that matter) including a conversion constructor looks like:
public class AddressDTO {
private String city;
// Conversion constructor
public AddressDTO(AddressEntity a) {
this.city = a.getCity();
}
#Override
public String toString() {
return "AddressDTO [city=" + getCity() + "]";
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
prints
StudentDTO [id=1, name=myStudent, address=AddressDTO [city=myCity]]
You can try to use SerializationUtils.clone(). This method deep clone your object. But you should mark your objects as Serializable.
https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/SerializationUtils.html#clone(T)
I have the following xml
<MyPojo>
<name>Jason</name>
<age>25</age>
<meta>
<occupation>Engineer</occupation>
</meta>
</MyPojo>
I need to deserialize it to the following POJO:
public class MyPojo {
private String name;
private int age;
private String occupation;
}
The problem here is that occupation is wrapped within meta element
You need one more object:
public class MyPojo {
private String name;
private int age;
private Meta meta;
}
public class Meta{
private String occupation;
}
My idea is to replace occupation with an own class. Something like myMeta or whatever you want to call it(be aware in your case like the xml says: meta). This class should cotain the field occupation:
public class Meta
{
private String occupation;
}
After that you only have to add a new field of your new class e.g. myMeta to myPojo. Something like this:
public class MyPojo
{
private String name;
private int age;
private Meta meta;
}
this should avoid
that occupation is wrapped within meta element
Hope that helps!