Mapping list size to int field with modelmapper - java

I am new to modelmapper. I have Department and Staff classes in my SpringBoot project. Department class has list of staffs. Using ModelMapper I want to create DepartmentDTO that has staffCount field. I have added Department and DepartmentDTO classes below. How to achive this mapping?
Department class
public class Department {
private Long id;
private String departmentName;
private Set<Staff> staffList = new HashSet<>();
public Department(String departmentName) {
super();
this.departmentName = departmentName;
}
// getters and setters
}
DepartmentDTO class
public class DepartmentDTO {
private Long id;
private String departmentName;
private int staffCount = 0;
public DepartmentDTO(String departmentName) {
this.departmentName = departmentName;
}
// getters and setters
}

I have found solution from this post. I have created DepartmentStaffListToStaffCountConverter class. And used it when adding mappings to modelmapper instance on SpringBootApplication configuration file.
DepartmentStaffListToStaffCountConverter
public class DepartmentStaffListToStaffCountConverter extends AbstractConverter<Set<Staff>, Integer> {
#Override
protected Integer convert(Set<Staff> staffList) {
if(staffList != null) {
return staffList.size();
} else {
return 0;
}
}
}
SpringBootApplication file
#SpringBootApplication
public class SpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplication.class, args);
}
#Bean
public ModelMapper getModelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.typeMap(Department.class, DepartmentDTO.class)
.addMappings(new PropertyMap<Department, DepartmentDTO>() {
#Override
protected void configure() {
using(new DepartmentStaffListToStaffCountConverter()).map(source.getStaffList(), destination.getStaffCount());
}
});
return modelMapper;
}
}

Related

Mapping nested JSON values in a Java class

I can't figure out how to handle nested JSON values in my Java classes. To make it simple as possible, I created four Java classes with every 'nested level'. However, I'm trying to have all these values in one Java class. How can I do this?
Json:
{
"_embedded":{
"events":[
{
"name":"KISS | End Of The Road World Tour",
"dates":{
"start":{
"dateTime":"2021-06-12T19:00:00Z"
},
"classifications":[
{
"name":"Rock"
}
],
"_embedded":{
"venues":[
{
"name":"Atlas Arena"
}
]
}
}
}
]
}
}
Java classes:
#Data
public class EventList {
#JsonProperty("_embedded")
private Events events;
}
#Data
public class Events {
#JsonProperty("events")
public List<EventDetails> eventsList;
}
#Data
public class EventDetails {
private String name;
#JsonProperty("dates.start.dateTime")
private String startDate;
#JsonProperty("classifications.genre.name")
private String musicType;
#JsonProperty("_embedded.venues")
private List<Venues> eventPlaceName;
}
#Data
public class Venues {
private String name;
}
You can club all the supporting classes in one class like below :
#Data
public class Start {
#JsonProperty("dateTime")
public Date dateTime;
public static class Venue {
#JsonProperty("name")
public String name;
}
#Data
public static class Classification {
#JsonProperty("name")
public String name;
}
#Data
public static class Embedded2 {
#JsonProperty("venues")
public List<Venue> venues;
}
#Data
public static class Dates {
#JsonProperty("start")
public Start start;
#JsonProperty("classifications")
public List<Classification> classifications;
#JsonProperty("_embedded")
public Embedded2 _embedded;
}
#Data
public static class Event {
#JsonProperty("name")
public String name;
#JsonProperty("dates")
public Dates dates;
}
#Data
public static class Embedded {
#JsonProperty("events")
public List<Event> events;
}
#Data
public static class Root {
#JsonProperty("_embedded")
public Embedded _embedded;
}
}
You can test (I am using Jackson for deserialization)
create ObjectMapper class and deserialize into a Root class
public class TestJson {
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
try {
Start.Root root = objectMapper.readValue(new File("C:\\Anurag\\Development\\CodeBase\\demo\\src\\main\\java\\com\\example\\demo\\domain\\testJson\\test.json"), Start.Root.class);
System.out.println(root);
} catch (Exception e) {
e.printStackTrace();
}
}
}
-When debugging, you'll notice that our objects have been filled accordingly:
**Changes done as per your requiement **
#Data
public class EventList {
#Getter
#JsonProperty("_embedded")
private Events events;
#Data
public static class Venue {
#JsonProperty("name")
public String name;
}
#Data
public static class Classification {
#JsonProperty("name")
public String name;
}
#Data
public static class Embedded2 {
#JsonProperty("venues")
public List<Venue> venues;
}
#Data
public static class Dates {
#JsonProperty("start")
public Start start;
#JsonProperty("classifications")
public List<Classification> classifications;
#JsonProperty("_embedded")
public Embedded2 _embedded;
}
#Data
public static class EventDetails {
#JsonProperty("name")
public String name;
#JsonProperty("dates")
public Dates dates;
}
#Data
public static class Events {
#JsonProperty("events")
public List<EventDetails> eventsList;
}
#Data
public static class Start {
#JsonProperty("dateTime")
public Date dateTime;
}
}
public class TestJson {
public static void main(String[] args) {
List<EventList.EventDetails> anyCity = findEventByCity("any city");
anyCity.stream().forEach(p-> {
System.out.println(p);
});
}
#SneakyThrows
static List<EventList.EventDetails> findEventByCity(String city) {
ObjectMapper objectMapper = new ObjectMapper();
EventList eventList = objectMapper.readValue(new File("C:\\Anurag\\Development\\CodeBase\\demo\\src\\main\\java\\com\\example\\demo\\domain\\testJson\\test.json"), EventList.class);
List<EventList.EventDetails> eventsList = eventList.getEvents().getEventsList();
return eventsList;
}
}

Parameter 5 of constructor in ... required a bean of type '...Mapper' that could not be found

Description:
Parameter 5 of constructor in com.example.springmysqlelastic.utils.ElasticSynchronizer required a bean of type 'com.example.springmysqlelastic.mapper.FoodMapper' that could not be found.
Action:
Consider defining a bean of type 'com.example.springmysqlelastic.mapper.FoodMapper' in your configuration.
There is a MySQL to Elasticsearch sync module and all was working for User entity. Then i added new Food entity. I set files. But now im getting Bean error on FoodMapper.
Project info: food, restaurant, user search on Elasticsearch engine with Spring.
FoodMapper.java
#Mapper(componentModel = "spring")
public interface FoodMapper {
FoodDTO toFoodDTO(Food food);
List<FoodDTO> toFoodDtos(List<Food> foods);
Food toFood(FoodDTO foodDTO);
List<Food> toFoods(List<FoodDTO> foodDTOS);
FoodModel toFoodModel(Food food);
}
UserMapper.java
#Mapper(componentModel = "spring")
public interface UserMapper {
UserDTO toUserDTO(User user);
List<UserDTO> toUserDtos(List<User> users);
User toUser(UserDTO userDTO);
List<User> toUsers(List<UserDTO> userDTOS);
UserModel toUserModel(User user);
}
FoodService.java
#Service
public class FoodService implements IFoodService {
private IFoodDAO foodDAO;
private FoodMapper foodMapper;
#Autowired
public FoodService(IFoodDAO foodDAO, FoodMapper foodMapper) {
this.foodDAO = foodDAO;
this.foodMapper = foodMapper;
}
#Override
public FoodDTO save(FoodDTO foodDTO) {
Food food = this.foodDAO.save(this.foodMapper.toFood(foodDTO));
return this.foodMapper.toFoodDTO(food);
}
#Override
public FoodDTO findById(Long id) {
return this.foodMapper.toFoodDTO(this.foodDAO.findById(id).orElse(null));
}
#Override
public List<FoodDTO> findAll() {
return this.foodMapper.toFoodDtos(this.foodDAO.findAll());
}
}
ElasticSynchorizer.java
#Service
public class ElasticSynchronizer {
private IUserDAO userDAO;
private IUserESRepo userESRepo;
private UserMapper userMapper;
private IFoodDAO foodDAO;
private IFoodESRepo foodESRepo;
private FoodMapper foodMapper;
private static final Logger LOG = LoggerFactory.getLogger(ElasticSynchronizer.class);
#Autowired
public ElasticSynchronizer(IUserDAO userDAO, IUserESRepo userESRepo, UserMapper userMapper, IFoodDAO foodDAO, IFoodESRepo foodESRepo, FoodMapper foodMapper) {
this.userDAO = userDAO;
this.userESRepo = userESRepo;
this.userMapper = userMapper;
this.foodDAO = foodDAO;
this.foodESRepo = foodESRepo;
this.foodMapper = foodMapper;
}
#Scheduled(cron = "0 */3 * * * *")
#Transactional
public void sync() {
LOG.info("Start Syncing Users - {}", LocalDateTime.now());
this.syncUsers();
LOG.info(" End Syncing Users - {}", LocalDateTime.now());
LOG.info("Start Syncing Foods- {}", LocalDateTime.now());
this.syncFoods();
LOG.info(" End Syncing Foods - {}", LocalDateTime.now());
}
private void syncUsers() {
Specification<User> userSpecification = (root, criteriaQuery, criteriaBuilder) ->
getModificationDatePredicate(criteriaBuilder, root);
List<User> userList;
if (userESRepo.count() == 0) {
userList = userDAO.findAll();
} else {
userList = userDAO.findAll(userSpecification);
}
for(User user: userList) {
LOG.info("Syncing User - {}", user.getId());
userESRepo.save(this.userMapper.toUserModel(user));
}
}
private void syncFoods() {
Specification<Food> userSpecification = (root, criteriaQuery, criteriaBuilder) ->
getModificationDatePredicate(criteriaBuilder, root);
List<Food> foodList;
if (userESRepo.count() == 0) {
foodList = foodDAO.findAll();
} else {
foodList = foodDAO.findAll(userSpecification);
}
for(Food food: foodList) {
LOG.info("Syncing Food - {}", food.getId());
foodESRepo.save(this.foodMapper.toFoodModel(food));
}
}
private static Predicate getModificationDatePredicate(CriteriaBuilder cb, Root<?> root) {
Expression<Timestamp> currentTime;
currentTime = cb.currentTimestamp();
Expression<Timestamp> currentTimeMinus = cb.literal(new Timestamp(System.currentTimeMillis() -
(Constants.INTERVAL_IN_MILLISECONDE)));
return cb.between(root.<Date>get(Constants.MODIFICATION_DATE),
currentTimeMinus,
currentTime
);
}
}
Food.java
#NoArgsConstructor
#AllArgsConstructor
#Data
#Entity
public class Food {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private float price;
private String category;
private String description;
private String imglink;
private String restaurant;
#Temporal(TemporalType.TIMESTAMP)
#UpdateTimestamp
private Date modificationDate;
}
Foodmodel.java
#NoArgsConstructor
#AllArgsConstructor
#Data
#Document(indexName = "food") //type deprecate oldu hepsi artik _doc
public class FoodModel {
private Long id;
private String name;
private float price;
private String category;
private String description;
private String imglink;
private String restaurant;
private Date modificationDate;
}
FoodDTO.java
#NoArgsConstructor
#AllArgsConstructor
#Data
public class FoodDTO {
private Long id;
private String name;
private float price;
private String category;
private String description;
private String imglink;
private String restaurant;
}
IFoodDAO.java
#Repository
public interface IFoodDAO extends JpaRepository<Food, Long>, JpaSpecificationExecutor<Food> {
//List<User> findByNameContaining(String name);
}
IFoodESRepo.java
public interface IFoodESRepo extends ElasticsearchRepository<FoodModel, Long> {
//List<Food> findByNameContaining(String name);
}
main class
#SpringBootApplication
#EnableElasticsearchRepositories("com.example.springmysqlelastic.repo.elastic")
#EnableScheduling
#EnableJpaRepositories("com.example.springmysqlelastic.repo")
//#ComponentScan(basePackages = {"com.example.springmysqlelastic"})
//#EnableAutoConfiguration
public class SpringMysqlElasticApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMysqlElasticApplication.class, args);
}
}
structure
enter image description here
enter image description here
I found that i can't use multiple mappers. Moved second mapper to first now works.
Don't use more than 1 mapper.

Mapstruct Bug with nested mapper and unmappedSourcePolicy

I want to map a EmployeeDto to a EmployeeValue. Consider the following classes:
public class EmployeeDto {
private String telephoneNumber;
private Integer companyId;
public String getTelephoneNumber() {
return telephoneNumber;
}
public void setTelephoneNumber(String telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
public Integer getCompanyId() {
return companyId;
}
public void setCompanyId(Integer companyId) {
this.companyId = companyId;
}
}
public class EmployeeValue {
private String telephoneNumber;
private Company company;
public String getTelephoneNumber() {
return telephoneNumber;
}
public void setTelephoneNumber(String telephoneNumber) {
this.telephoneNumber = telephoneNumber;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
public class Company {
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
I am trying to map those classes using a mapstruct-mapper:
#Mapper(componentModel = "cdi")
public interface EmployeeDto2EmployeeValueMapper {
#Mapping(source ="companyId", target = "company.id")
EmployeeValue map(EmployeeDto dto);
}
This is working just perfectly fine, as this test runs green:
class EmployeeDto2EmployeeValueMapperTest {
private static final String TELEPHONE_NUMBER = "telephoneNumber";
private static final int COMPANY_ID = 1;
private EmployeeDto2EmployeeValueMapper classUnderTest = Mappers.getMapper(EmployeeDto2EmployeeValueMapper.class);
#Test
void map() {
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setTelephoneNumber(TELEPHONE_NUMBER);
employeeDto.setCompanyId(COMPANY_ID);
EmployeeValue outcome = classUnderTest.map(employeeDto);
assertThat(outcome.getTelephoneNumber(), is(TELEPHONE_NUMBER));
assertThat(outcome.getCompany().getId(), is(COMPANY_ID));
}
}
Now, if I add unmappedSourcePolicy = ReportingPolicy.ERROR to the mapper, that is
#Mapper(componentModel = "cdi", unmappedSourcePolicy = ReportingPolicy.ERROR)
public interface EmployeeDto2EmployeeValueMapper {
#Mapping(source ="companyId", target = "company.id")
EmployeeValue map(EmployeeDto dto);
}
the build fails with the following error message:
Unmapped source properties: "telephoneNumber".
To me this seems like a bug, because those field actually got mapped (as my test proved before).
Do you have any ideas on this?
With respect to the mapstruct issue tracker the following entry seems to cover your question.
Issue: unmappedSourcePolicy set to ERROR leads to a nested Bean issue #1881
https://github.com/mapstruct/mapstruct/issues/1881
According to issue comments a fix will be included in the upcoming release 1.4.0. However, I could not discover when this release will happen.

mongodb auditing in spring boot for saving createdDate, lastModifiedDate, createdBy, lastModifiedBy

I am using spring boot, therefore I am not using any xml files for configurations.
What I have to do is to EnableMongoAuditing for saving createdDate, lastModifiedDate etc while saving data using MongoRepositories.
My model class
#Component
#Document(collection = "CAPPING")
public class TemporaryCapping extends BaseEntity {
#Field("contract_id")
private BigInteger contractId;
#Field("period_id")
private BigInteger periodId;
#Field("user_id")
private BigInteger userId;
#Field("amount")
private Double amount;
#Field("type_of_capping")
private TypeOfCapping typeOfCapping;
public BigInteger getContractId() {
return contractId;
}
public void setContractId(BigInteger contractId) {
this.contractId = contractId;
}
public BigInteger getPeriodId() {
return periodId;
}
public void setPeriodId(BigInteger periodId) {
this.periodId = periodId;
}
public BigInteger getUserId() {
return userId;
}
public void setUserId(BigInteger userId) {
this.userId = userId;
}
public Double getAmount() {
return amount;
}
public void setAmount(Double amount) {
this.amount = amount;
}
public TypeOfCapping getTypeOfCapping() {
return typeOfCapping;
}
public void setTypeOfCapping(TypeOfCapping typeOfCapping) {
this.typeOfCapping = typeOfCapping;
}
}
public class BaseEntity implements Serializable{
#Id
#Indexed(unique = true)
private BigInteger id;
#CreatedDate
private DateTime createdDate;
#Field("modified_date")
private BigInteger modifiedDate;
public BigInteger getId() {
return id;
}
public void setId(BigInteger id) {
this.id = id;
}
public DateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(DateTime createdDate) {
this.createdDate = createdDate;
}
public BigInteger getModifiedDate() {
return modifiedDate;
}
public void setModifiedDate(BigInteger modifiedDate) {
this.modifiedDate = modifiedDate;
}
I have used #CreateDate annotation for saving createDate.
and I have used jodatime dependency for DateTime
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.7</version>
</dependency>
spring-data-mongodb is also added in the dependencies.
This is my main application class
#SpringBootApplication
#EnableMongoAuditing
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Where I am wrong in this impelmentation as the date is not getting saved in database?
Also I know that for saving #createdBy you need to write AuditorAware bean but for now I am just trying to save createdBy.
Where should #EnableMongoAuditing be used?
In my application I configure through Java code. I use #EnableMongAuditing this way and also create convertes for ZonedDateTime.
#Configuration
#EnableMongoAuditing
#EnableMongoRepositories(basePackages = { BASE_PACKAGE })
public class MongoConfiguration extends AbstractMongoConfiguration {
public static final String BASE_PACKAGE = "package.with.aggregates";
#Value("${spring.data.mongodb.uri}")
private String mongoUri;
#Value("${spring.data.mongodb.database}")
private String databaseName;
#Override
protected String getDatabaseName() {
return databaseName;
}
#Override
public Mongo mongo() throws Exception {
return new MongoClient(new MongoClientURI(mongoUri));
}
// Here you must add converters to Joda datetypes. In my solution is ZonedDateTime
#Override
public CustomConversions customConversions() {
List<Converter<?, ?>> converterList = new ArrayList<>();
converterList.add(new DateToZonedDateTimeConverter());
converterList.add(new ZonedDateTimeToDateConverter());
return new CustomConversions(converterList);
}
#Override
protected String getMappingBasePackage() {
return BASE_PACKAGE;
}
}
#EnableMongoAuditing can actually be placed anywhere in configurations (next to #Configuration annotation)

Orika - Map object to null if all fields of object are null

I got 2 classes A and B which both got a company. The company of A has got a little more information than the company of B (Company has an id while CNCompany doesn´t). I want to map all fields using orika. If all fields an object are null, I want the object to be null!
I tried to express this with an unit test. What has to be done to get this running?
public class A {
private Company company;
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
public class B {
private CNCompany company;
public CNCompany getCompany() {
return company;
}
public void setCompany(CNCompany company) {
this.company = company;
}
}
public class Company {
private Id id;
private AccountId accountId;
public Id getId() {
return id;
}
public void setId(Id id) {
this.id = id;
}
public AccountId getAccountId() {
return accountId;
}
public void setAccountId(AccountId accountId) {
this.accountId = accountId;
}
}
public class CNCompany {
private AccountId accountId
public AccountId getAccountId() {
return accountId;
}
public void setAccountId(AccountId accountId) {
this.accountId = accountId;
}
}
public class MyMapper extends ConfigurableMapper {
#Override
protected void configure(MapperFactory factory) {
factory.classMap(A.class, B.class) //
.mapNulls(false).mapNullsInReverse(false) //
.byDefault() //
.register();
}
}
#Test
public void testMap() throws Exception {
A a = new A();
Company company = new Company();
Id id = new Id();
id.setValue("1");
company.setId(id);
a.setCompany(company);
MyMapper myMapper = new MyMapper();
B outcome = myMapper.map(a, B.class);
assertThat(outcome.getCompany(), is(nullValue()));
}
If I understand correctly you want getCompany to return null if the Company object contains only null values.
In Orika you can control conversion with a custom converter. For your example that might look something like:
public class CompanyConverter extends CustomConverter<Company, CNCompany> {
public CNCompany convert(Company source, Type<? extends CNCompany> destinationType) {
if (isNothingButNulls(source)) {
return null;
}
final CNCompany newCompany = new CNCompany();
// do your thing
return newCompany;
}
}
I've never written a CustomConverter that can return null so I'm not 100% sure this will work but it should. Note that the converter will still need to be registered. The documentation I linked shows how to register depending on what level you want the converter at.

Categories

Resources