Model Mapper for data response

By | September 20, 2016

1. Fund Adaptor

Suppose that Fund Adaptor executes a service to search the response data- a monthly or daily (depending on the schedule). This example is to show how the customer details response data is mapped. There are two data transfer objects (DTO) namely CustomerDTO (maybe to present in UI) and CustomerSvcDTO (from executing store procedures or calling services). Moreover, the data responded should support paging.

2. Solution

– Use ModelMapper to convert one model to another, which makes it possible for us to maintain, refactor, implement easily and quickly.
– Define Pagination that support previous, next, first, last, to name just a few.

3. Design.

3.1 Class diagram

message-reponse-mapping

Figure 1: A part of class diagram for model mapper

3.2 Project structure

message-response-mapping-project-structure

Figure 2: Data (so-called message) response project structure.

4. Implementation.

4.1 DTOs.

src/main/java/com/fund/adaptor/message/common/Identifiable.java

package com.fund.adaptor.message.common;

import java.io.Serializable;
public interface Identifiable extends Serializable {
}

src/main/java/com/fund/adaptor/message/common/ToStringAllFieldsSupport.java

package com.fund.adaptor.message.common;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
public class ToStringAllFieldsSupport {
	  private static final String[] EXCLUDED_FIELD_NAMES = new String[]{"password"};

	  public ToStringAllFieldsSupport() {
	  }

	  public String toString() {
	       return (new ReflectionToStringBuilder(this))
               .setExcludeFieldNames(EXCLUDED_FIELD_NAMES).toString();
	  }
}

src/main/java/com/fund/adaptor/message/common/DTO.java

package com.fund.adaptor.message.common;

import net.logstash.logback.encoder.org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class DTO extends ToStringAllFieldsSupport implements Identifiable {
	public DTO() {
    }

    public boolean equals(Object other) {
   // This method uses reflection to determine if the two Objects are equal.
        return EqualsBuilder.reflectionEquals(this, other, false);
    }

    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this, new String[0]);
    }
}

src/main/java/com/fund/adaptor/message/common/SearchResultPageLinksDTO.java

package com.fund.adaptor.message.common;

public class SearchResultPageLinksDTO extends DTO {
    private String previous;
    private String next;
    private String first;
    private String last;

   // Add default constructor, setter and getter, equal and hashCode.
}

src/main/java/com/fund/adaptor/message/common/PagingResultDTO.java

package com.fund.adaptor.message.common;

import java.util.ArrayList;
import java.util.List;

public abstract class PagingResultDTO <D extends DTO> {
    private List<D> results = new ArrayList();
    private Integer offset;
    private Integer total;
    private Integer limit;

    public PagingResultDTO() {
    }
   // Add setter and getter.
}

src/main/java/com/fund/adaptor/message/common/UIPagingResultsDTO.java

package com.fund.adaptor.message.common;

public class UIPagingResultsDTO <D extends DTO> extends PagingResultDTO<D> {
    private SearchResultPageLinksDTO links = new SearchResultPageLinksDTO();
    public UIPagingResultsDTO() {
    }

    public SearchResultPageLinksDTO getLinks() {
        return links;
    }

    public void setLinks(SearchResultPageLinksDTO links) {
        this.links = links;
    }
}

src/main/java/com/fund/adaptor/message/dto/CustomerSvcDTO.java

package com.fund.adaptor.message.dto;

import com.fund.adaptor.message.common.DTO;
import org.joda.time.LocalDateTime;
/**
 * DTO response data from a micro-service.
 */
public class CustomerSvcDTO extends DTO {
	private Integer customerNum;
	private String trustInvestorName;
	private String addressStreet;
	private String addressCity;
	private String addressCountry;
	private LocalDateTime dateOfBirth;
	private Integer returnCode;
     
    // Add constructor with fields, setter and getter, equals and hashCode
}

src/main/java/com/fund/adaptor/message/dto/CustomerDTO.java

package com.fund.adaptor.message.dto;

import com.fund.adaptor.message.common.DTO;
import org.joda.time.LocalDateTime;
import javax.validation.constraints.NotNull;
/**
 * DTO presentation.
 */
public class CustomerDTO extends DTO {
	@NotNull
	private Integer custNum;
	private String name;
	private String street;
	private String city;
	private String country;
	private LocalDateTime dob;
	private Integer returnCode;

   // Add constructor with fields, setter and getter, equals and hashCode
}	

src/main/java/com/fund/adaptor/message/dto/CustomerSvcSearchResultsDTO.java

package com.fund.adaptor.message.dto;

import com.fund.adaptor.message.common.PagingResultDTO;

public class CustomerSvcSearchResultsDTO extends PagingResultDTO<CustomerSvcDTO> {
}

src/main/java/com/fund/adaptor/message/dto/CustomerSearchResultsDTO .java

package com.fund.adaptor.message.dto;

import com.fund.adaptor.message.common.UIPagingResultsDTO;

public class CustomerSearchResultsDTO extends UIPagingResultsDTO<CustomerDTO> {
}

4.2 Model Mapper

src/main/java/com/fund/adaptor/message/dto/CustomerMapper.java

package com.fund.adaptor.message.dto;

import org.springframework.stereotype.Component;
import java.lang.reflect.Type;
import java.util.List;
import org.modelmapper.ModelMapper;
import org.modelmapper.PropertyMap;
import org.modelmapper.TypeToken;
/**
 * Customer Message Mapper.
 */
@Component
public class CustomerMapper {
	 public ModelMapper modelMapper = new ModelMapper();
	  
	 public CustomerMapper() {
		 modelMapper.addMappings(new ToCustomerSvcDTOMapping());
	     modelMapper.addMappings(new ToCustomerDTOMapping());
	 }
	 /** Mapping from CustomerSvcDTo to CustomerDTO **/
	 public CustomerDTO toCustomerDTO(CustomerSvcDTO customerSvcDTO) {
		 return modelMapper.map(customerSvcDTO, CustomerDTO.class);
	 }
	 /** Mapping from CustomerDTO to CustomerSvcDTO **/
	 public CustomerSvcDTO toCustomerSvcDTO(CustomerDTO customerDTO) {
		 return modelMapper.map(customerDTO, CustomerSvcDTO.class);
	 }
	 /** Mapping from CustomerSvcDTO List to CustomerDTO List **/
	 public List<CustomerDTO> toCustomerDTOList(List<CustomerSvcDTO> customerSvcDTOList) {
		 Type listType =  new TypeToken<List<CustomerDTO>>() {}.getType();
		 List<CustomerDTO> customerDTOs = modelMapper.map(customerSvcDTOList, listType);
		 return customerDTOs;
	 }

	 static class ToCustomerSvcDTOMapping extends PropertyMap<CustomerDTO, CustomerSvcDTO> {
		 /** Define mapping configure PropertyMap <Source, Destination> **/
		 @Override
		 protected void configure() {
			 map().setCustomerNum(source.getCustNum());
			 map().setTrustInvestorName(source.getName());
			 map().setAddressStreet(source.getStreet());
			 map().setAddressCity(source.getCity());
			 map().setAddressCountry(source.getCountry());
			 map().setDateOfBirth(source.getDob());
			 skip().setReturnCode(null);
		 }
	 }

	static class ToCustomerDTOMapping extends PropertyMap<CustomerSvcDTO, CustomerDTO> {
		/** Define mapping configure PropertyMap <Source, Destination> **/
		@Override
		protected void configure() {
			map().setCustNum(source.getCustomerNum());
			map().setName(source.getTrustInvestorName());
			skip().setStreet(null);
			skip().setCity(null);
			skip().setCountry(null);
			skip().setDob(source.getDateOfBirth());
			map().setReturnCode(source.getReturnCode());
		}
	}
}

4.3 Service

src/main/java/com/fund/adaptor/message/service/CustomerMessageService.java

package com.fund.adaptor.message.service;

import com.fund.adaptor.message.dto.CustomerSearchResultsDTO;

public interface CustomerMessageService {
    CustomerSearchResultsDTO getCustomerMessage(String someStuff, 
                                   Integer offset, Integer limit);
}

src/main/java/com/fund/adaptor/message/service/CustomerMessageService.java

package com.fund.adaptor.message.service;

import com.fund.adaptor.message.dto.*;
import com.fund.adaptor.message.gateway.CustomerMessageGateway;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

import static java.util.stream.Collectors.toList;

@Service
public class CustomerMessageServiceImpl implements CustomerMessageService{
    @Autowired
    private CustomerMessageGateway customerMessageGateway;

    @Autowired
    private CustomerMapper customerMapper;

    /**
     * Get Customer Message with paging.
     * @param someStuff put your parameters
     * @param offset page number.
     * @param limit number of records per page.
     * @return CustomerSearchResultsDTO
     */
    public CustomerSearchResultsDTO getCustomerMessage(String someStuff,
                                                       Integer offset, Integer limit) {

        CustomerSvcSearchResultsDTO customerSvcSearchResults =
                customerMessageGateway.getCustomerMessageResponse(someStuff, offset, limit);
        CustomerSearchResultsDTO results = new CustomerSearchResultsDTO();

        final List<CustomerDTO> customerDTOs = customerSvcSearchResults.getResults()
                .stream()
                .map(customerSvcDTO -> customerSvcDTOToCustomerDTO(customerSvcDTO, 1))
                //.map(this::customerSvcDTOToCustomerDTO)
                .collect(toList());

        results.setResults(customerDTOs);
        results.setOffset(customerSvcSearchResults.getOffset());
        results.setLimit(customerSvcSearchResults.getLimit());
        results.setTotal(customerSvcSearchResults.getTotal());
        return results;
    }

    private CustomerDTO customerSvcDTOToCustomerDTO(
                                   CustomerSvcDTO customerSvcDTO,int messageCode) {
        CustomerDTO customerDTO = customerMapper.toCustomerDTO(customerSvcDTO);
        /** If message code #1 unsuccessful execution, 
         then update message code #2 - do more action**/
        if(customerDTO.getReturnCode() == messageCode) {
            customerDTO.setReturnCode(2);
        }
        return customerDTO;
    }
    private CustomerDTO customerSvcDTOToCustomerDTO(CustomerSvcDTO customerSvcDTO) {
        return customerMapper.toCustomerDTO(customerSvcDTO);
    }
}

4.4 Application executable

src/main/java/com/fund/adaptor/message/Application.java

package com.fund.adaptor.message;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import java.io.IOException;

@Configuration
@ComponentScan(basePackages = {"com.fund.adaptor.message"})
@EnableAutoConfiguration

public class Application {
    private static final Logger LOG = LoggerFactory.getLogger(Application.class);

    @Autowired
    private Environment env;

    @PostConstruct
    public void initApplication() throws IOException {
        if (env.getActiveProfiles().length == 0) {
            LOG.warn("No Spring profile configured, running with default configuration");
        } else {
            LOG.info("Running with Spring profile(s) : {}", env.getActiveProfiles());
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4.5 pom.xml

message-response-mapping/pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.fund.adaptor</groupId>
	<artifactId>message-reponse-mapping</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>message-reponse-mapping</name>
	<url>http://maven.apache.org</url>
	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-autoconfigure</artifactId>
			<version>1.2.7.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>4.1.8.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
			<version>4.1.8.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>4.3.2.RELEASE</version>
		</dependency>
		<!-- Hibernate -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>5.2.4.Final</version>
		</dependency>
		<!-- Module Mapping -->
		<dependency>
			<groupId>org.modelmapper</groupId>
			<artifactId>modelmapper</artifactId>
			<version>0.7.5</version>
		</dependency>
		<dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.1.0.Final</version>
		</dependency>
		<dependency>
			<groupId>net.logstash.logback</groupId>
			<artifactId>logstash-logback-encoder</artifactId>
			<version>4.2</version>
		</dependency>
		<!-- Utils -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.4</version>
		</dependency>
		<dependency>
			<groupId>joda-time</groupId>
			<artifactId>joda-time</artifactId>
			<version>2.9.4</version>
		</dependency>
		<!-- Logging  -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.21</version>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>1.1.3</version>
		</dependency>
		<!-- Testing -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.12</version>
		</dependency>
	</dependencies>
</project>

5. Testing

src/test/java/com/fund/adaptor/message/mapper/CustomerMapperTest .java

package com.fund.adaptor.message.mapper;

import org.junit.Test;
import com.fund.adaptor.message.dto.CustomerMapper;

public class CustomerMapperTest {
    @Test
    public void validateMappingTest(){
       new CustomerMapper().modelMapper.validate();
    }
}

src/test/java/com/fund/adaptor/message/service/CustomerServiceITTest.java

package com.fund.adaptor.message.service;


import com.fund.adaptor.message.Application;
import com.fund.adaptor.message.dto.CustomerDTO;
import com.fund.adaptor.message.dto.CustomerSearchResultsDTO;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebAppConfiguration
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@ActiveProfiles("devmock")
public class CustomerServiceITTest {
    @Autowired
    private CustomerMessageService customerMessageService;
    @Test
    public void getCustomerMessageResponseTest() {
        CustomerSearchResultsDTO resultsDTO = customerMessageService
                                         .getCustomerMessage("", 0, 5);
        Assert.assertNotNull(resultsDTO);
        Assert.assertEquals(4, resultsDTO.getTotal().intValue());
        Assert.assertNotNull(resultsDTO.getResults());
        List<CustomerDTO> customerDTOList = resultsDTO.getResults();
        customerDTOList.forEach(customerDTO -> {
                    if(8082 == customerDTO.getCustNum().intValue()) {
                        Assert.assertEquals("Return MessageCode expected is 2",
                                2, customerDTO.getReturnCode().intValue());
                    } else {
                        Assert.assertEquals("Return MessageCode expected is 0",
                                0, customerDTO.getReturnCode().intValue());
                    }
                });
    }
}

Download Zip here

References:

One thought on “Model Mapper for data response

  1. khiem Post author

    Alternately, Lightweight model mapping with Java 8

    private Function<Book,BookDTO> bookToBookDTO = new Function<Book, BookDTO>() {
        public BookDTO apply(Book book) { 
    return new BookDTO(book.getIsbn(), book.getTitle(),book.getAuthor());}
    };
     
    // usage:
     
    // Map one:
    bookToBookDTO.apply(aBookEntity);
     
    // Map a list:
    aBookEntityList.stream().map(bookToBookDTO).collect(Collectors.toList());
    
    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.