Total Pageviews

2017/11/08

[MapStruct] How to use Decorator in MapStruct?

Problem
Assume I have a DeliveryAddressDto Java bean, it has three attributes:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package albert.practice.mapStruct;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class DeliveryAddressDto {
    private String receiver;
    private String addressString;
    private String totalString;
}

I have three Java beans, they provide information which DeliveryAddressDto needed.
1. Person bean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package albert.practice.mapStruct;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Person {
    private String firstName;
    private String lastName;
}


2. Address bean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package albert.practice.mapStruct;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Address {
    private String addressString;
}


3. ShoppingItems bean
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package albert.practice.mapStruct;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class ShoppingItems {
    private Long total;
}


The three attributes values in DeliveryAddressDto Java bean will copy from Person, Address, and ShoppingItems bean
(1) DeliveryAddressDto.receiver = Person.firstName + " " + Person.lastName
(2) DeliveryAddressDto.addressString = Address.addressString
(3) DeliveryAddressDto.totalString = ShoppingItems.total (Apply format: NT$#,###,###,##0)




How to use decorator to fulfill this requirement?
 

How-To
1. create a mapper class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package albert.practice.mapStruct.mapper;

import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import albert.practice.mapStruct.Address;
import albert.practice.mapStruct.DeliveryAddressDto;
import albert.practice.mapStruct.Person;
import albert.practice.mapStruct.ShoppingItems;
import albert.practice.mapStruct.decorator.AddressDecorator;

@Mapper
@DecoratedWith(AddressDecorator.class)
public interface AddressMapper {
    // By convention, a mapper interface should define a member called INSTANCE
    // which holds a single instance of the mapper type:
    AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class);

    @Mapping(target = "totalString", source = "shoppingItems.total")
    @Mapping(target = "receiver", source = "person.lastName")
    @Mapping(target = "addressString", source = "address.addressString")
    DeliveryAddressDto covertPersonAndAddressToDeliveryAddressDto(Person person, Address address,
      ShoppingItems shoppingItems);

}

2. create a decorator class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package albert.practice.mapStruct.decorator;

import java.text.DecimalFormat;

import albert.practice.mapStruct.Address;
import albert.practice.mapStruct.DeliveryAddressDto;
import albert.practice.mapStruct.Person;
import albert.practice.mapStruct.ShoppingItems;
import albert.practice.mapStruct.mapper.AddressMapper;

public class AddressDecorator implements AddressMapper {

    private final AddressMapper delegate;
    private DecimalFormat decimalFormat = new DecimalFormat("NT$#,###,###,##0");

    public AddressDecorator(AddressMapper delegate) {
        this.delegate = delegate;
    }

    @Override
    public DeliveryAddressDto covertPersonAndAddressToDeliveryAddressDto(Person person, Address address,
      ShoppingItems shoppingItems) {
        DeliveryAddressDto dto = delegate.covertPersonAndAddressToDeliveryAddressDto(person, address, shoppingItems);
        dto.setReceiver(person.getFirstName() + " " + person.getLastName());
        dto.setTotalString(decimalFormat.format(shoppingItems.getTotal()));
        return dto;
    }

}




2017/11/07

Using MapStruct to simplify the implementation of mappings between Java bean types

Problem
Assume I have two java bean classes, Car and CarDto, I would like to copy Car attributes to CarDto attributes, and vice versa.

The Car class is as bellows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package albert.practice.mapStruct;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Car {
    private String manufacturer;
    private int seatCount;
    private String mfgDateString;
}


The CarDto class is as following:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package albert.practice.mapStruct;

import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class CarDto {
    private String make;
    private int numberOfSeats;
    private Date mfgDate;
}



The two Java bean classes have two differenet name and even have different data type, how to fulfill this requirement conveniently?




How-To
You can make good use of MapStruct to simplify the implementation of mappings between Java bean types.

Add the following configuration in pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    <properties>
        <org.mapstruct.version>1.1.0.Final</org.mapstruct.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
            <version>${org.mapstruct.version}</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source> <!-- or higher, depending on your project -->
                    <target>1.8</target> <!-- or higher, depending on your project -->  
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>


Create a CarMapper to do column mapping
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package albert.practice.mapStruct.mapper;

import java.util.List;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import albert.practice.mapStruct.Car;
import albert.practice.mapStruct.CarDto;

@Mapper(uses = MfgDateMapper.class)
public interface CarMapper {
    // By convention, a mapper interface should define a member called INSTANCE
    // which holds a single instance of the mapper type:
    CarMapper CarMapper = Mappers.getMapper(CarMapper.class);

    @Mapping(target = "mfgDate", source = "mfgDateString")
    @Mapping(target = "make", source = "manufacturer")
    @Mapping(target = "numberOfSeats", source = "seatCount")
    CarDto convertCarToCarDto(Car car);

    @Mapping(target = "mfgDateString", source = "mfgDate")
    @Mapping(target = "manufacturer", source = "make")
    @Mapping(target = "seatCount", source = "numberOfSeats")
    Car convertCarDtoToCar(CarDto carDto);
    
}




Create a MfgDateMapper class to convert date string to java.util.Date, and vice versa 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package albert.practice.mapStruct.mapper;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.google.common.base.Strings;

public class MfgDateMapper {

    private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public Date toDate(String dateString) throws ParseException {

        Date date = null;
        if (!Strings.isNullOrEmpty(dateString)) {
            try {
                date = dateFormat.parse(dateString);
            } catch (ParseException e) {
                throw e;
            }
        }
        return date;
    }

    public String toDateString(Date date) {
        String dateString = "";
        if (date != null) {
           dateString = dateFormat.format(date);
        }
        return dateString;
    }

}


Create a test class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package albert.practice.mapStruct;

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.mapstruct.factory.Mappers;

import albert.practice.mapStruct.mapper.CarMapper;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CarMapperTest {

    CarMapper carMapper;

    @Before
    public void setup() {
        carMapper = Mappers.getMapper(CarMapper.class);
    }

    @Test
    public void testConvertCarToCarDto() {
        Car car = new Car("Toyota Camry", 5, "2017-04-01");
        CarDto carDto = carMapper.convertCarToCarDto(car);

        log.debug("[testConvertCarToCarDto] car = " + car.toString());
        log.debug("[testConvertCarToCarDto] carDto = " + carDto.toString());

        assertTrue(carDto.getMake().equals(car.getManufacturer()));
        assertTrue(carDto.getNumberOfSeats() == car.getSeatCount());
    }

    @Test
    public void testConvertCarDtoToCar() {
        CarDto carDto = new CarDto("MINI Cooper S", 4, new Date());
        Car car = carMapper.convertCarDtoToCar(carDto);

        log.debug("[testConvertCarDtoToCar] carDto = " + carDto.toString());
        log.debug("[testConvertCarDtoToCar] car = " + car.toString());

        assertTrue(car.getManufacturer().equals(carDto.getMake()));
        assertTrue(car.getSeatCount() == carDto.getNumberOfSeats());
    }

}






2017/11/06

[Angular] How to implement pagination in ng-grid

Problem
How to implement pagination in ng-grid?

How-to

Here has code snippet:

1. enable pagination function, and set pagingOptions & totalServerItems in ng-grid

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    $scope.gridCols  = [ 
        {  field : 'no',              displayName : '序號',         width : '5%',   cellClass : 'text-right' }, 
        {  field : 'exceptionGroup',  displayName : '異常類別',     width : '15%',  cellClass : 'text-left' }, 
        {  field : 'exceptionDesc',   displayName : '異常類別說明', width : '25%',  cellClass : 'text-left' }, 
        {  field : 'deptName',        displayName : '部門別',       width : '15%',  cellClass : 'text-left' }, 
        {  field : 'userName',        displayName : '通報人員姓名',  width : '10%',  cellClass : 'text-left' }, 
        {  field : 'emailAddr',       displayName : '收件Email',    width : '30%',  cellClass : 'text-left' } 
    ];
        
    $scope.dataGrid = {
        multiSelect : false,
        data : 'itemData',
        columnDefs : 'gridCols',
        enableColumnResize : true,
        keepLastSelected : false,
        enablePaging : true,
        showFooter : true,
        pagingOptions : $scope.pagingOptions,
        totalServerItems : "totalServerItems"
    };


2. set initial values when page loaded
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    // query result (current page)
    $scope.itemData = [];
    // query result (complete data)
    $scope.resultData = [];     
        
    // set page sizes, page size and current page
    $scope.pagingOptions = {
        pageSizes : [ 25, 50, 100 ],
        pageSize : 25,
        currentPage : 1
    };
    // total items are on the server (initial value)
    $scope.totalServerItems = 0;        

3. set data to current page
1
2
3
4
5
6
7
8
9
    // set paging data
    $scope.setPagingData = function(data, page, pageSize) {
        var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
        $scope.itemData = pagedData;
        $scope.totalServerItems = data.length;
        if (!$scope.$$phase) {
            $scope.$apply();
        }
    };


4. Registers a listener callback to be executed whenever the pagingOptions changes.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    // Registers a listener callback to be executed whenever the pagingOptions changes.
    $scope.$watch('pagingOptions', function(newVal, oldVal) {
        // if page size change, then set current page to 1 and set paging data
        if(newVal.pageSize !== oldVal.pageSize) {
            $scope.pagingOptions.currentPage = 1;
            $scope.setPagingData($scope.resultData, $scope.pagingOptions.currentPage, newVal.pageSize);
        }
   
        // if current page change, then set paging data
        if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
            $scope.setPagingData($scope.resultData, newVal.currentPage, newVal.pageSize);
        }
    }, true);     


Screenshot is as bellows:



Reference

[1] https://angular-ui.github.io/ui-grid/

2017/11/05

[Java] Make a unique list of objects

Problem
I am using do query to retrieve a List of Cam101wGridData data. The data contains user name and email address. 
User asks to remove duplicate email address from the data list, how to do it?
1
    List<Cam101wGridData> result= doQuery(form, result);


How-To
Steps are as following:
Step1. Create a Cam101wEmailCsvBean and override equals and hashCode methods (using email attribute)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    package x.x.x.model;
    
    import java.io.Serializable;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.NoArgsConstructor;
    
    /**
     * Mail格式下載 bean (解決 Email 重複的資料)
     *
     * Any class definition may be annotated with @EqualsAndHashCode to let lombok generate
     * implementations of the equals(Object other) and hashCode() methods. By default, it'll use all
     * non-static, non-transient fields, but you can exclude more fields by naming them in the optional
     * exclude parameter to the annotation. Alternatively, you can specify exactly which fields you wish
     * to be used by naming them in the of parameter.
     */
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @EqualsAndHashCode(of = "email")
    public class Cam101wEmailCsvBean implements Serializable {
        private static final long serialVersionUID = 1L;
    
        /**
         * 會員姓名
         */
        private String userName;
    
        /**
         * E-Mail
         */
        private String email;
    
    }


Step2. Convert from List<Cam101wGridData>  to List<Cam101wEmailCsvBean>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
   /**
     * 將 List<Cam101wGridData> 轉成 List<Cam101wEmailCsvBean>.
     * 
     * @param result List<Cam101wGridData>
     * @return List<Cam101wEmailCsvBean>
     */
    private List<Cam101wEmailCsvBean> convertToCam101wEmailCsvBean(List<Cam101wGridData> result) {
        List<Cam101wEmailCsvBean> emailCsvBeans = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(result)) {
            for (Cam101wGridData row : result) {
                Cam101wEmailCsvBean emailBean = new Cam101wEmailCsvBean(row.getUserName(),
                        row.getEmail());
                emailCsvBeans.add(emailBean);
            }
        }
        return emailCsvBeans;
    }


Step3. Convert List to Set (remove duplicate email), and convert Set to List 
1
2
3
4
5
6
7
8
   private List<Cam101wEmailCsvBean> getDistinctEmailCsvBean(List<Cam101wEmailCsvBean> emailCsvBeans) {
       List<Cam101wEmailCsvBean> distinctEmailCsvBeans = new ArrayList<>();
       
       Set<Cam101wEmailCsvBean> emailCsvSet = new HashSet<>(emailCsvBeans);
       distinctEmailCsvBeans.addAll(emailCsvSet);
       
       return distinctEmailCsvBeans;
   }


Code snippet (Before and after)
1
2
3
4
5
6
7
    // before (with duplicate emails)
    List<Cam101wGridData> result= doQuery(form, result);
    
    // after (with distinct email)
    List<Cam101wGridData> result= doQuery(form, result);
    List<Cam101wEmailCsvBean> emailCsvBeans = convertToCam101wEmailCsvBean(result);
    List<Cam101wEmailCsvBean> distinctEmailCsvBeans = getDistinctEmailCsvBean(emailCsvBeans);




Reference
[1] https://projectlombok.org/features/EqualsAndHashCode.html    
[2] https://stackoverflow.com/questions/20126470/java-how-to-keep-unique-values-in-a-list-of-objects




2017/11/04

[Eclipse] How to know current worksapce ?

Problem
How do I know which eclipse workspace I am working currently?

How-To

File->Switch Workspace->Other... and it will show the name of current workspace.


2017/11/03

SMS Service with Failsafe Mechanism

Scenario


How-To
Making good use of failsafe library can let you handle failure easily.
Remember to add dependency into your pom.xml
1
2
3
4
5
6
        <!-- retry -->
        <dependency>
            <groupId>net.jodah</groupId>
            <artifactId>failsafe</artifactId>
            <version>1.0.4</version>
        </dependency>


Sample code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    private void sendSmsWithRetry(final String otp, final SmsParams params) {
        final String mobilePhone = params.getMobilePhone();
        final CallerEnum sourceName = params.getSourceName();
        final String policyNumber = params.getPolicyNumber();
        final String userId = params.getUserId();

        RetryPolicy retryPolicy = new RetryPolicy();
        retryPolicy.withDelay(1, TimeUnit.SECONDS); // Sets the delay between retries.
        retryPolicy.withMaxRetries(5);              // Sets the max number of retries to perform. -1 indicates to retry forever.

        // Creates and returns a new SyncFailsafe instance that will perform executions and 
        // retries synchronously according to the retryPolicy.
        Failsafe.with(retryPolicy)
        // Registers the listener to be called when an execution is successful.
        .onSuccess(new CheckedConsumer() {
            @Override
            public void accept(Object t) throws Exception {
                // The execution is successful, then write success log into database
            }
        })
        // Registers the listener to be called when an execution attempt fails.
        .onFailedAttempt(new CheckedConsumer() {
            @Override
            public void accept(Object t) throws Exception {
                if (t instanceof Exception) {
                    Exception e = (Exception) t;
                    // The execution attempt fails, then write error log into database
                }
            }
        })
        // Registers the listener to be called when an execution fails and cannot be retried.
        .onFailure(new ContextualResultListener() {
            @Override
            public void onResult(Object result, Throwable failure, ExecutionContext context)
                    throws Exception {
                String exeMsg = "驗證碼傳送失敗。手機號碼:" + mobilePhone + ", 失敗原因:" + failure.getMessage();
                failure.printStackTrace();                
                throw new RuntimeException(exeMsg);
            }
        })
        // Executes the runnable until successful or until the configured RetryPolicy is exceeded.
        .run(new CheckedRunnable() {
            @Override
            public void run() throws Exception {
                sendSms(mobilePhone, getSmsText(params.getOtpServiceType(), otp));
            }
        });
    }

2017/11/02

[Java] MessageFormat

Problem
How to takes a set of objects, formats them, then inserts the formatted strings into the pattern at the appropriate places.

Example 1.



Example 2.



How-To

Here has an example to demonstrate how to do message format:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package albert.practice.stringFormat;

import java.text.Format;
import java.text.MessageFormat;

public class StringFormatExample {
    
    public String getFormattedMessage(String message, String... values) {
       Format messageFormat = new MessageFormat(message);
       return messageFormat.format(values);
    }
    
}


Test case is as bellows:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package albert.practice.stringFormat;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class StringFormatExampleTest {

    private StringFormatExample stringFormat;
    private String unformatedMessage = "";
    private String phone = "";
    private String otp;
    private String anotherUnformatedMessage;
    private String error;
    
    @Before
    public void setup() {
        stringFormat = new StringFormatExample();
        unformatedMessage = "驗證碼傳送成功。手機號碼: {0}, 簡訊內容:驗證碼為:{1}";
        anotherUnformatedMessage = "驗證碼傳送失敗,失敗原因:{1}。手機號碼: {0}";
        phone = "0910123456";
        otp = "12345";
        error = "HTTP Server is down";
    }
    
    @Test
    public void testReturnWith1000Separator() {
        String str = stringFormat.returnWith1000Separator(1234567890);
        log.debug(str);
        assertEquals("1,234,567,890", str);
    }
    
    @Test
    public void testGetFormattedMessage() {
        String formatedMsg = stringFormat.getFormattedMessage(unformatedMessage, phone, otp);
        log.debug("formatedMsg = " + formatedMsg);
        assertEquals("驗證碼傳送成功。手機號碼: 0910123456, 簡訊內容:驗證碼為:12345", formatedMsg);
        
        String anotherFormatedMsg = stringFormat.getFormattedMessage(anotherUnformatedMessage, phone, error);
        log.debug("anotherFormatedMsg = " + anotherFormatedMsg);
        assertEquals("驗證碼傳送失敗,失敗原因:HTTP Server is down。手機號碼: 0910123456", anotherFormatedMsg);
    }

}
 

print logs:
1
2
14:57:18.679 [main] DEBUG a.p.s.s - formatedMsg = 驗證碼傳送成功。手機號碼: 0910123456, 簡訊內容:驗證碼為:12345
14:57:18.679 [main] DEBUG a.p.s.s - anotherFormatedMsg = 驗證碼傳送失敗,失敗原因:HTTP Server is down。手機號碼: 0910123456