Total Pageviews

2018/11/14

[Java 8] Optional Example

Scenario


How-To
1. 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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>albert</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>test</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2. Create a Person class
package albert.test;

import lombok.Builder;
import lombok.Data;
import lombok.ToString;

@Data
@Builder
@ToString
public class Person {

    private Integer id;
    private String name;
    private String gender;
    
}

3. Create a custom exception
package albert.test;

public class PersonNotFoundException extends Exception {

    public PersonNotFoundException() {
        super();
    }

    public PersonNotFoundException(String message, Throwable cause, boolean enableSuppression,
            boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public PersonNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }

    public PersonNotFoundException(String message) {
        super(message);
    }

    public PersonNotFoundException(Throwable cause) {
        super(cause);
    }

}

4. Create a OptionalTest class
package albert.test;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OptionalTest {

    private Person albert = Person.builder().id(1).name("Albert").gender("Male").build();
    private Person mandy = Person.builder().id(2).name("Mandy").gender("Female").build();
    private Person eric = Person.builder().id(3).name("Graham").gender("Male").build();
    private List<Person> people = Arrays.asList(albert, mandy, eric);

    public Person getByNameByOldWay(String name) throws PersonNotFoundException {
        Person result = null;
        for (Person p : people) {
            if (p.getName().equals(name)) {
                result = p;
                break;
            }
        }
        if (result == null) {
            throw new PersonNotFoundException("Cannot find " + name);
        }
        return result;
    }

    public Person getByNameOrElseThrow(String name) throws PersonNotFoundException {
        Optional<Person> person = people.stream().filter(p -> name.equals(p.getName())).findFirst();
        return person.isPresent() ? person.get()
                : person.orElseThrow(() -> new PersonNotFoundException("Cannot find " + name));
    }

}


Reference
[1] Optional in Java 8 cheat sheet

2018/11/13

[Spring Framework] @Scheduled Annotation in Spring

Scenario
If I have two batch services:
(1) execute a task every 5 minute, and execute 5 second delay before the first execution
(2) execute a task on 17:20 everyday 

How-To
Add dependency in your pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>


Create a scheduler service
package com.example.demo.service;

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

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class SchedulerService {

    private final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    /*
     * Schedule a Task with Initial Delay
     */
    @Scheduled(fixedRate = 300000, initialDelay = 5000)
    public void scheduleFixedRateTask() {
        System.out.println("Fixed rate task - " + formatter.format(new Date()));
    }

    /*
     * Schedule a Task using Cron Expressions
     */
    @Scheduled(cron = "0 20 17 * * ?")
    public void scheduleTaskUsingCronExpression() {
        System.out.println("scschedule Task Using Cron Expression - " + formatter.format(new Date()));
    }

}


Create a client application
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class DemoApplication {

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


Execution Log:
2018-06-04 17:11:22.474  INFO 14272 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.686 seconds (JVM running for 1.268)
Fixed rate task - 2018-06-04 17:11:27.473
Fixed rate task - 2018-06-04 17:16:27.472
scschedule Task Using Cron Expression - 2018-06-04 17:20:00.002
Fixed rate task - 2018-06-04 17:21:27.473
Fixed rate task - 2018-06-04 17:26:27.472
Fixed rate task - 2018-06-04 17:31:27.472

2018/11/12

[Java] javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed

Scenario

發生 PKIX path building failed 錯誤的程式碼 (Line 82)
  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
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
package test.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import test.vo.Bank;
import test.vo.PostOffice;

public class OpenDataService {

    private static final String BANK_OPEN_DATA_URL = "https://quality.data.gov.tw/dq_download_csv.php?nid=6041&md5_url=4cbb7958872cb677421021ef63b2e2b9";

    public List<Bank> parseBankData() throws IOException {

        String csvData = "";
        try {
            csvData = getBankData();
        } catch (Exception e1) {
            throw new RuntimeException("金融機構基本資料 取得失敗, 錯誤原因: " + e1.getMessage(), e1);
        }

        List<Bank> bankData = new ArrayList<>();

        String[] FILE_HEADER = { "類別", "總機構代號", "分支機構代號", "機構名稱", "地址", "緯度", "經度", "電話", "負責人", "異動日期", "金融機構網址" };
        CSVFormat csvFileFormat = CSVFormat.DEFAULT.withHeader(FILE_HEADER);

        try (CSVParser csvFileParser = new CSVParser(new StringReader(csvData), csvFileFormat);) {
            List<CSVRecord> csvRecords = csvFileParser.getRecords();
            for (int i = 1; i < csvRecords.size(); i++) {
                CSVRecord record = csvRecords.get(i);
                String type = record.get("類別");
                String headquarterCode = record.get("總機構代號");
                String branchCode = record.get("分支機構代號");
                String branchName = record.get("機構名稱");
                String address = record.get("地址");
                String latitude = record.get("緯度");
                String longitude = record.get("經度");
                String phone = record.get("電話");
                String representative = record.get("負責人");
                String updateDate = record.get("異動日期");
                String url = record.get("金融機構網址");

                Bank bank = Bank.builder().type(type).headquarterCode(headquarterCode).branchCode(branchCode)
                        .branchName(branchName).address(address).latitude(latitude).longitude(longitude).phone(phone)
                        .representative(representative).updateDate(updateDate).url(url).build();
                bankData.add(bank);
            }
        } catch (IOException e) {
            throw new RuntimeException("金融機構基本資料 解析失敗, 錯誤原因: " + e.getMessage(), e);
        }
        return bankData;
    }

    public String getBankData() throws Exception {
        String bankDataStr = "";
        InputStream inputStream = null;
        try (CloseableHttpClient httpClient = HttpClients.createDefault();) {
      
            HttpGet httpGet = new HttpGet(BANK_OPEN_DATA_URL);

            HttpResponse httpResponse = httpClient.execute(httpGet);

            inputStream = httpResponse.getEntity().getContent();
            bankDataStr = IOUtils.toString(inputStream, "UTF-8");
        } catch (IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
        return bankDataStr;
    }

}


錯誤訊息如下:
Exception in thread "main" java.lang.RuntimeException: 金融機構基本資料 取得失敗, 錯誤原因: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at test.service.OpenDataService.parseBankData(OpenDataService.java:41)
    at test.OpenDataClient.main(OpenDataClient.java:12)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1959)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
    at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
    at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1514)
    at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
    at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1026)
    at sun.security.ssl.Handshaker.process_record(Handshaker.java:961)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1072)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1385)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1413)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1397)


How-To
依據參考文獻,透過暴力法,overwrite TrustManager,使其忽略檢查、校驗。程式碼改寫如下:
  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
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package test.service;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import test.vo.Bank;
import test.vo.PostOffice;

public class OpenDataService {

    private static final String BANK_OPEN_DATA_URL = "https://quality.data.gov.tw/dq_download_csv.php?nid=6041&md5_url=4cbb7958872cb677421021ef63b2e2b9";

    public List<Bank> parseBankData() throws IOException {

        String csvData = "";
        try {
            csvData = getBankData();
        } catch (Exception e1) {
            throw new RuntimeException("金融機構基本資料 取得失敗, 錯誤原因: " + e1.getMessage(), e1);
        }

        List<Bank> bankData = new ArrayList<>();

        String[] FILE_HEADER = { "類別", "總機構代號", "分支機構代號", "機構名稱", "地址", "緯度", "經度", "電話", "負責人", "異動日期", "金融機構網址" };
        CSVFormat csvFileFormat = CSVFormat.DEFAULT.withHeader(FILE_HEADER);

        try (CSVParser csvFileParser = new CSVParser(new StringReader(csvData), csvFileFormat);) {
            List<CSVRecord> csvRecords = csvFileParser.getRecords();
            for (int i = 1; i < csvRecords.size(); i++) {
                CSVRecord record = csvRecords.get(i);
                String type = record.get("類別");
                String headquarterCode = record.get("總機構代號");
                String branchCode = record.get("分支機構代號");
                String branchName = record.get("機構名稱");
                String address = record.get("地址");
                String latitude = record.get("緯度");
                String longitude = record.get("經度");
                String phone = record.get("電話");
                String representative = record.get("負責人");
                String updateDate = record.get("異動日期");
                String url = record.get("金融機構網址");

                Bank bank = Bank.builder().type(type).headquarterCode(headquarterCode).branchCode(branchCode)
                        .branchName(branchName).address(address).latitude(latitude).longitude(longitude).phone(phone)
                        .representative(representative).updateDate(updateDate).url(url).build();
                bankData.add(bank);
            }
        } catch (IOException e) {
            throw new RuntimeException("金融機構基本資料 解析失敗, 錯誤原因: " + e.getMessage(), e);
        }
        return bankData;
    }

    public String getBankData() throws Exception {
        String bankDataStr = "";
        InputStream inputStream = null;
        try (CloseableHttpClient httpClient = createHttpsClient();) {

            HttpGet httpGet = new HttpGet(BANK_OPEN_DATA_URL);

            HttpResponse httpResponse = httpClient.execute(httpGet);

            inputStream = httpResponse.getEntity().getContent();
            bankDataStr = IOUtils.toString(inputStream, "UTF-8");
        } catch (NoSuchAlgorithmException | KeyManagementException | IOException e) {
            throw e;
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
        return bankDataStr;
    }

    /**
     * 解決 PKIX path building failed 問題
     * 
     * @return HttpClient
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    private CloseableHttpClient createHttpsClient() throws NoSuchAlgorithmException, KeyManagementException {
        // Create a trust manager that does not validate certificate chains
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        } };

        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, trustAllCerts, null);

        LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);

        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
        return httpclient;
    }

}



Reference
[1] http://www.aneasystone.com/archives/2016/04/java-and-https.html