do I something wrong or si this bug in Mapstruct? I am having this mapper definition:
#Mapping(target = "isEnabled", ignore = true)
#Mapping(target = "isCredentialsNonExpired", ignore = true)
#Mapping(target = "isAccountNonLocked", ignore = true)
#Mapping(target = "isAccountNonExpired", ignore = true)
#Mapping(target = "grantedAuthorities", ignore = true)
#Mapping(target = "userName", source = "email")
UserCredentials toUserCredentials(User user);
and MapStruct generates this implementation:
#Component
public class UserCredentialsMapperImpl implements UserCredentialsMapper {
#Override
public UserCredentials toUserCredentials(User user) {
if ( user == null ) {
return null;
}
String userName = null;
String password = null;
userName = user.getEmail();
password = user.getPassword();
boolean isEnabled = false;
boolean isCredentialsNonExpired = false;
boolean isAccountNonLocked = false;
boolean isAccountNonExpired = false;
Collection<? extends GrantedAuthority> grantedAuthorities = null;
UserCredentials userCredentials = new UserCredentials( grantedAuthorities, password, userName, isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled );
return userCredentials;
}
}
as you can see even though I have set isEnabled to ignore it is setting it to false instead of ignoring it...
EDITED:
After comments from Federico klez Culloca I realized, that MapStruct is doing everything right. The problem was in my logic. I wanted to have default values for those ignored fields, but because my class was immutable and I had all arg constructor, MapStruct had to insert some value into those fields and that is why it inserted there false value. I was able to solve this problem using Builder pattern, now MapStruct sets only the correct fields and default values are used for the rest. Thanks all for help.
#Override
public UserCredentials toUserCredentials(User user) {
if ( user == null ) {
return null;
}
UserCredentialsBuilder userCredentials = UserCredentials.builder();
userCredentials.userName( user.getEmail() );
userCredentials.password( user.getPassword() );
return userCredentials.build();
}
Related
I'm trying to make a mapping using MapStruct but I don't know how to deal with the fields from one to the other.
I have the classes below:
class DataDomain {
private List<Domain> data;
}
class Domain {
private String codDist;
private String numFun;
private String txtJust;
private Boolean valPar;
private LocalDateTime dateHr;
private Integer numPn;
}
class DataEntity {
private String codDist;
private String numFun;
private List<ParEntity> pares;
}
class ParEntity {
private String numFun;
private String txtJus;
private String indValPar;
private String dateHr;
private String numPn;
}
interface ParOutMapper{
ParOutMapper INSTANCE = Mappers.getMapper(ParOutMapper.class);
#Mapping(target = "data", source = "entity")
DataDomain map(DataEntity entity);
Domain toDomain(DataEntity entity);
default List<Domain> toList(DataEntity entity) {
return entity != null ? singletonList(toDomain(entity)) : new ArrayList<>();
}
default DataEntity map(DataDomain domain) {
return domain != null
&& domain.getData() != null
&& !domain.getData().isEmpty() ? toEntity(domain.getData().get(0)) : null;
}
DataEntity toEntity(Domain domains);
List<Domain> toDomainList(List<DataEntity> domainList);
}
That's what I've done so far, but it's giving divergence in the mapping because both have different structures and I ended up getting lost in how to apply their origin and destination field to field.
If possible and someone knows how to do it in an interesting correct way I would be very grateful.
I would suggest the following solution
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR,
componentModel = "spring",
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
builder = #Builder(disableBuilder = true))
public interface ParOutMapper {
#Mapping(target = "data", source = "entity")
DataDomain map(DataEntity entity);
#Mapping(target = "txtJust", source = "pares", qualifiedByName = "txtJust")
#Mapping(target = "valPar", source = "pares", qualifiedByName = "valPar")
#Mapping(target = "dateHr", source = "pares", qualifiedByName = "dateHr")
#Mapping(target = "numPn", source = "pares", qualifiedByName = "numPn")
Domain toDomain(DataEntity entity);
default List<Domain> toList(DataEntity entity) {
return entity != null ? singletonList(toDomain(entity)) : new ArrayList<>();
}
default DataEntity map(DataDomain domain) {
return domain != null
&& domain.getData() != null
&& !domain.getData().isEmpty() ? toEntity(domain.getData().get(0)) : null;
}
#Mapping(target = "pares", ignore = true)
DataEntity toEntity(Domain domains);
List<Domain> toDomainList(List<DataEntity> domainList);
#AfterMapping
default DataEntity valuesToList(Domain domains, #MappingTarget DataEntity dataEntity){
ParEntity parEntity = new ParEntity();
parEntity.setDateHr(domains.getDateHr().toString()); // alternative call custom entity to list mapping here !
parEntity.setTxtJus(domains.getTxtJust());
parEntity.setNumPn(domains.getNumPn().toString());
parEntity.setNumFun(domains.getNumFun());
parEntity.setIndValPar(domains.getValPar().toString());
dataEntity.setPares(List.of(parEntity));
return dataEntity;
}
#Named("txtJust")
default String mapTxtJust(List<ParEntity> pares) {
return pares.get(0).getTxtJus(); // or custom mapping logic here
}
#Named("valPar")
default Boolean mapValPar(List<ParEntity> pares) {
return Boolean.valueOf(pares.get(0).getIndValPar()); // or custom mapping logic here
}
#Named("dateHr")
default LocalDateTime mapDateHr(List<ParEntity> pares) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
return LocalDateTime.parse(pares.get(0).getDateHr(),formatter); // or custom mapping logic here
}
#Named("numPn")
default Integer mapNumPn(List<ParEntity> pares) {
return Integer.valueOf(pares.get(0).getNumPn()); // or custom mapping logic here
}
}
Since you tagged your question with spring-boot i assume you are using it. Therefore i would suggest to use the provided component model by mapstruct in its configuration
I am unsure how you want to do your mapping of list to entitiy or entity to list. With my approach you can do it value by value or with the entire list. Both workes either way.
The solution compiles and workes for DataEntity toEntity(Domain domains); and Domain toDomain(DataEntity entity); i did not recognize any other problems since mapstruct is able to generate the required mappings.
Sample Objects
public class Source {
String fromPersonFirstName;
String fromPersonLastName;
String toPersonFirstName;
String toPersonLastName;
String message;
}
public class Target {
Person from;
Person to;
String message;
}
public class Person {
String firstName;
String lastName;
}
#Mapper(componentModel = "spring")
public interface PersonMapper {
Person toPerson(String firstName, String lastName);
}
The Question
Now, what would be a clean way of employing as much as mapstruct as possible? Without using expressions?
Ideally I'd tell mapstruct to do the following
#Mapper(componentModel = "spring", uses = PersonMapper.class)
public interface ExampleMapper {
#Mapping(target = "from", source = "fromPersonFirstName, fromPersonLastName")
#Mapping(target = "to", source = "toPersonFirstName, toPersonLastName")
Target toTarget(Source s);
}
Alternatively I could annotate PersonMapper and toPerson, then for the toTarget method qualifiedByName = "TheClassNameValue, TheMethodNameValue". But I still don't know how to tell the method which source fields to use.
What would be another option? I could use a local method toPerson, qualifiedByName. But then I'd have to import the other mapper in some way. Maybe change the ExampleMapper to abstract and autowire the other mapper. But I still can't specify multiple fields.
I know that expression is probably meant to fill in this gap. But ... I would rather never use it if possible. Unless the alternative is considerably more complicated.
Solution 1, Mapping method selection based on qualifiers
#Mapper
public interface ExampleMapper {
#Mapping(target = "from", source = "source", qualifiedByName = "fromPersonMapping")
#Mapping(target = "to", source = "source", qualifiedByName = "toPersonMapping")
Target toTarget(Source source);
#Named("fromPersonMapping")
#Mapping(target = "firstName", source = "fromPersonFirstName")
#Mapping(target = "lastName", source = "fromPersonLastName")
Person fromPersonMapping(Source source);
#Named("toPersonMapping")
#Mapping(target = "firstName", source = "toPersonFirstName")
#Mapping(target = "lastName", source = "toPersonLastName")
Person toPersonMapping(Source source);
}
Generated code:
public class ExampleMapperImpl implements ExampleMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.from = fromPersonMapping( source );
target.to = toPersonMapping( source );
target.message = source.message;
return target;
}
#Override
public Person fromPersonMapping(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.fromPersonFirstName;
person.lastName = source.fromPersonLastName;
return person;
}
#Override
public Person toPersonMapping(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.toPersonFirstName;
person.lastName = source.toPersonLastName;
return person;
}
}
Alternative with custom methods:
#Mapper
public interface ExampleMapper {
#Mapping(target = "from", source = "source", qualifiedByName = "fromFieldsMapping")
#Mapping(target = "to", source = "source", qualifiedByName = "toFieldsMapping")
Target toTarget(Source source);
Person toPerson(String firstName, String lastName);
#Named("fromFieldsMapping")
default Person fromFieldsMapping(Source source) {
return toPerson(source.fromPersonFirstName, source.fromPersonLastName);
}
#Named("toFieldsMapping")
default Person toFieldsMapping(Source source) {
return toPerson(source.toPersonFirstName, source.toPersonLastName);
}
}
Generated code:
public class ExampleMapperImpl implements ExampleMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.from = fromFieldsMapping( source );
target.to = toFieldsMapping( source );
target.message = source.message;
return target;
}
#Override
public Person toPerson(String firstName, String lastName) {
if ( firstName == null && lastName == null ) {
return null;
}
Person person = new Person();
if ( firstName != null ) {
person.firstName = firstName;
}
if ( lastName != null ) {
person.lastName = lastName;
}
return person;
}
}
With separate mapper:
#Mapper(uses = PersonMapper.class)
public interface ExampleMapper1 {
#Mapping(target = "from", source = "source", qualifiedBy = FromAnnotation.class)
#Mapping(target = "to", source = "source", qualifiedBy = ToAnnotation.class)
Target toTarget(Source source);
}
#Mapper
public interface PersonMapper {
Person toPerson(String firstName, String lastName);
#FromAnnotation
default Person fromFieldsMapping(Source source) {
return toPerson(source.fromPersonFirstName, source.fromPersonLastName);
}
#ToAnnotation
default Person toFieldsMapping(Source source) {
return toPerson(source.toPersonFirstName, source.toPersonLastName);
}
}
#Qualifier
#java.lang.annotation.Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface ToAnnotation {
}
#Qualifier
#java.lang.annotation.Target(ElementType.METHOD)
#Retention(RetentionPolicy.CLASS)
public #interface FromAnnotation {
}
Solution 2, Expression
#Mapper
public interface ExampleMapper {
#Mapping(target = "from", expression = "java(toPerson(source.fromPersonFirstName, source.fromPersonLastName))")
#Mapping(target = "to", expression = "java(toPerson(source.toPersonFirstName, source.toPersonLastName))")
Target toTarget(Source source);
Person toPerson(String firstName, String lastName);
}
Generated code:
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.message = source.message;
target.from = toPerson(source.fromPersonFirstName, source.fromPersonLastName);
target.to = toPerson(source.toPersonFirstName, source.toPersonLastName);
return target;
}
#Override
public Person toPerson(String firstName, String lastName) {
if ( firstName == null && lastName == null ) {
return null;
}
Person person = new Person();
if ( firstName != null ) {
person.firstName = firstName;
}
if ( lastName != null ) {
person.lastName = lastName;
}
return person;
}
Solution 3, regular mapping
#Mapper
public interface ExampleMapper {
#Mapping(target = "from.firstName", source = "source.fromPersonFirstName")
#Mapping(target = "from.lastName", source = "source.fromPersonLastName")
#Mapping(target = "to.firstName", source = "source.toPersonFirstName")
#Mapping(target = "to.lastName", source = "source.toPersonLastName")
Target toTarget(Source source);
}
Generated code:
public class ExampleMapperImpl implements ExampleMapper {
#Override
public Target toTarget(Source source) {
if ( source == null ) {
return null;
}
Target target = new Target();
target.from = sourceToPerson( source );
target.to = sourceToPerson1( source );
target.message = source.message;
return target;
}
protected Person sourceToPerson(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.fromPersonFirstName;
person.lastName = source.fromPersonLastName;
return person;
}
protected Person sourceToPerson1(Source source) {
if ( source == null ) {
return null;
}
Person person = new Person();
person.firstName = source.toPersonFirstName;
person.lastName = source.toPersonLastName;
return person;
}
}
I am trying to build some custom queries like the documentation is saying, but it is not working.
I got stuck in what i think is the block method and not any data is retrieved.
Here is my repo class:
#Repository
public interface UserRepository extends FirestoreReactiveRepository<User> {
Flux<User> findByCPF(String CPF);
Flux<User> findByEmail(String email);
}
and here is the model:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Document(collectionName = "userCollection")
public class User extends BaseEntity {
#NotNull(message = "{validation.notnull.user.name}")
private String name;
#Email
#NotNull(message = "{validation.notnull.user.email}")
private String email;
#NotNull(message = "{validation.notnull.user.password}")
private String password;
#NotNull(message = "{validation.notnull.user.address}")
private String address;
#CPF
#NotNull(message = "{validation.notnull.user.cpf}")
private String CPF;
#NotNull(message = "{validation.notnull.user.birthDate}")
private Date birthDate;
#NotNull(message = "{validation.notnull.user.role}")
private UserRoleEnum role;
}
this is the method that i'm building that i get stuck:
#Override
public Mono<User> create(User entity) {
var cpf = userRepository.findByCPF(entity.getCPF());
var email = userRepository.findByEmail(entity.getEmail());
if(cpf == null || email == null)
throw new EntityExistException();
else
return super.create(entity);
}
Yeah, it's an override because i have a generic service class:
public class GenericService<T>{
private FirestoreReactiveRepository<T> firestoreReactiveRepository;
public GenericService(FirestoreReactiveRepository<T> firestoreReactiveRepository){
this.firestoreReactiveRepository = firestoreReactiveRepository;
}
#Transactional
public Mono<T> create(T entity){
return firestoreReactiveRepository.save(entity);
}
**** HIDED CONTENT *****
Do you know how custom queries can be built with this FirestoreReactiveRepository ?
Of what types are:
var cpf = userRepository.findByCPF(entity.getCPF());
var email = userRepository.findByEmail(entity.getEmail());
?
You can write them also like:
Flux<User> cpf = userRepository.findByCPF(entity.getCPF());
Flux<User> email = userRepository.findByEmail(entity.getEmail());
I see three problems:
You don't subscribe to the cpf and email fluxes
The condition inside if is always false because both fluxes are not null
if(cpf == null || email == null)
... assuming that cpf and email would not be Flux<User> but User I think the condition should be cpf != null || email != null
The code could look more like this:
#Override
public Mono<User> create(User entity) {
var cpf = userRepository.findByCPF(entity.getCPF()); // Flux<User> cpf = ...
var email = userRepository.findByEmail(entity.getEmail()); // Flux<User> email = ...
return cpf.mergeWith(email)
.<User>handle((user, sink) -> sink.error(new EntityExistException()))
.switchIfEmpty(super.create(entity))
.next();
}
I'm trying to set a defaultValue for a boolean field using MapStruct, but the generated code simply ignores it.
My code:
public class CreateEventRequest {
#NotNull
#JsonProperty
private Boolean isPrivate;
#JsonProperty
private Boolean friendCanInviteFriends;
#JsonProperty
private boolean showGuestList;
public boolean isPrivate() {
return isPrivate;
}
public String getDescription() {
return description;
}
public boolean isFriendCanInviteFriends() {
return friendCanInviteFriends;
}
public boolean isShowGuestList() {
return showGuestList;
}
}
My mapper:
#Mapper(componentModel = "spring")
public interface CreateEventRequestMapper {
#Mapping(target = "showGuestList", source = "showGuestList", defaultExpression = "java(true)")
#Mapping(target = "friendCanInviteFriends", source = "friendCanInviteFriends", defaultValue = "true")
Event map(CreateEventRequest request);
}
The generated code:
public class CreateEventRequestMapperImpl implements CreateEventRequestMapper {
#Override
public Event map(CreateEventRequest request) {
if ( request == null ) {
return null;
}
Event event = new Event();
event.setShowGuestList( request.isShowGuestList() );
event.setFriendCanInviteFriends( request.isFriendCanInviteFriends() );
event.setPrivate( request.isPrivate() );
return event;
}
}
As you can see, I've tried using primitive/non-primitive type but it simply ignores the defaultValue.
Am I missing something here?
Thanks!
The problem is that the return type of your getter methods in the source object is always primitive which can't be null, you need to return Boolean.
MapStruct doesn't support direct private field access which requires reflection.
I am developing a chat system using Spring boot. At this point I am required to display all the users where isAdmin field is set to false in the database.
This is the query for the getByAdmin in DAO class:
public User getByAdmin(boolean isAdmin) {
return (User) getSession().createQuery(
"from User where isAdmin = :isAdmin")
.setParameter("isAdmin", isAdmin);
}
In my controller.java this is the point where I am calling the above method to make the check if the admin is set to true:
#ResponseBody
#RequestMapping(value = "/get-all-users", method = RequestMethod.GET)
public List<User> getAllUsers() {
try {
boolean isAdmin = true;
if(_userDao.getByAdmin(isAdmin).equals(true))
{
return _userDao.getAll();
}
} catch (Exception e) {
logger.error("Exception in fetching users: ", e.getStackTrace());
}
return null;
}
this is my bean definition for the Users.java
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "userId")
private Long id;
#Column(nullable = false)
private String name;
#Column(unique = true, nullable = false)
private String email;
#Column(nullable = false)
private long timestamp;
#Column(nullable = true)
private boolean isAdmin;
this is where I am making the check so nothing was returned
try {
boolean isAdmin = true;
if(_userDao.getByAdmin(isAdmin).equals(true))
{
return _userDao.getAll();
}
if I remove the above check I will be able to see all users
My challenge is that nothing was returned calling getAllUsers method.
Please what could be wrong with my logic?
if(_userDao.getByAdmin(isAdmin).equals(true))
You are comparing User with boolean. Probably you wanted to check something like:
if(_userDao.getByAdmin(isAdmin) != null)
?