Total Pageviews

Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

2020/12/02

[Java] [IntelliJ] Plugin "Lombok" is incompatible with the newest IntelliJ

Problem
After I updated IntelliJ, it showed error message as bellows:
Plugin "Lombok" is incompatible with the newest IntelliJ

How-To
1. Uninstall Lombok plugin
2. Reinstall Lombok plugin
3. Restart IntelliJ

2020/12/01

[Java] [JUnit] [SpringBoot] How to assign active profile in unit test?

Requirement
If I have multiple profiles in Spring Boot project, how to assign specific profile in my unit test program?


How-To
Added @ActiveProfiles onto your unit test class:
package com.test.tool.filegenerator.ftl.cp937;

import com.test.tool.filegenerator.util.Cp973FileUtils;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@ActiveProfiles("office")
public class TS0110_050110_70118_R01FileTest {

    @Autowired
    private Cp973FileUtils cp973FileUtils;

    @ParameterizedTest
    @ValueSource(strings = {"delivery/test data/LN3112P2.LN3112P2"})
    public void testReadTS0110_050110_70118_R01(String file) {
        Assertions.assertThatCode(
                () -> cp973FileUtils.readAndPrintContent(file)).doesNotThrowAnyException();
    }

}


2020/11/10

[Java] [Spring] How to configure Spring profile?

Problem
I am using jsoup to download excel file from google drive via Java.

I need to connect to google drive via http proxy in office, and can connect to google drive without proxy at home.

How to use Spring profile to configure to fit my different working environment?


How-To
1. Create a DownloadService Interface
package com.cht.tool.filegenerator.service;

import java.io.IOException;

public interface DownloadService {

    void downloadExcel() throws IOException;

}

2. Create a JSoupDefaultService serivce and mark with default profile
package com.cht.tool.filegenerator.service;

import lombok.extern.slf4j.Slf4j;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@Slf4j
@Service
@Profile({"default"})
public class JSoupDefaultService implements DownloadService {

    @Value("${jsoup.from}")
    private String from;

    @Value("${jsoup.to}")
    private String to;

    public void downloadExcel() throws IOException {
        log.debug("default profile");
        Connection.Response response = null;
        try {
            response = Jsoup.connect(from)
                    .ignoreContentType(true)
                    .execute();
        } catch (IOException e) {
            throw new IOException("fail to connect to google drive, error : " + e.getMessage(), e);
        }
        log.debug("status code = {}", response.statusCode());

        Files.write(Paths.get(to), response.bodyAsBytes());
        log.debug("excel file downloaded.");
    }

}

3. Create a JSoupService service and mark with office profile
package com.cht.tool.filegenerator.service;

import lombok.extern.slf4j.Slf4j;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@Slf4j
@Service
@Profile("office")
public class JSoupService implements DownloadService {

    @Value("${jsoup.from}")
    private String from;

    @Value("${jsoup.to}")
    private String to;

    @Value("${jsoup.proxy}")
    private String proxy;

    @Value("${jsoup.port}")
    private int port;

    public void downloadExcel() throws IOException {
        log.debug("office profile");
        Connection.Response response = null;
        try {
            response = Jsoup.connect(from)
                    .proxy(proxy, port)
                    .ignoreContentType(true)
                    .execute();
        } catch (IOException e) {
            throw new IOException("fail to connect to google drive, error : " + e.getMessage(), e);
        }
        log.debug("status code = {}", response.statusCode());

        Files.write(Paths.get(to), response.bodyAsBytes());
        log.debug("excel file downloaded.");
    }

}


4. Create a default profile in yaml
spring:
  profiles:
    active: default


5. Write Test code in Spring Application
package com.cht.tool.filegenerator;

import com.cht.tool.filegenerator.service.DownloadService;
import com.cht.tool.filegenerator.service.FileGeneratorService;
import com.cht.tool.filegenerator.service.JSoupService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;

import java.nio.file.Files;
import java.nio.file.Paths;

@Slf4j
@SpringBootApplication
public class FileGeneratorApplication {

    @Value("${jsoup.to}")
    private String to;

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

    @Bean
    CommandLineRunner run(FileGeneratorService fileGeneratorService, DownloadService downloadService) {
        return args -> {
            if (Files.exists(Paths.get(to))) {
                Files.delete(Paths.get(to));
            }
            downloadService.downloadExcel();

            fileGeneratorService.writePropertyFile();
            fileGeneratorService.writeFtpPropertyFile();
            fileGeneratorService.writeFtlFile();
            fileGeneratorService.writeSqlFile();
        };
    }
}

6. Do test
2020-07-24 11:18:50.529 DEBUG 6628 --- [           main] c.c.t.f.FileGeneratorApplication         : Running with Spring Boot v2.3.1.RELEASE, Spring v5.2.7.RELEASE
2020-07-24 11:18:50.529  INFO 6628 --- [           main] c.c.t.f.FileGeneratorApplication         : The following profiles are active: default
2020-07-24 11:18:51.328  INFO 6628 --- [           main] c.c.t.f.FileGeneratorApplication         : Started FileGeneratorApplication in 1.355 seconds (JVM running for 2.489)
2020-07-24 11:18:51.343 DEBUG 6628 --- [           main] c.c.t.f.service.JSoupDefaultService      : default profile
2020-07-24 11:18:53.501 DEBUG 6628 --- [           main] c.c.t.f.service.JSoupDefaultService      : status code = 200
2020-07-24 11:18:53.732 DEBUG 6628 --- [           main] c.c.t.f.service.JSoupDefaultService      : excel file downloaded.





2020/11/01

[Apache Freemarker] Custom date/time format example

Purpose
FreeMarker allows you to define your own number and date/time/datetime formats, and associate a name to them.

Here has an example to demonstrate how to convert date from 2020/07/16 to 109/07/16



How-To
1. Create custom number format class:
package com.cht.tool.filegenerator.ftl.custom;

import freemarker.core.Environment;
import freemarker.core.TemplateDateFormat;
import freemarker.core.TemplateDateFormatFactory;
import freemarker.core.TemplateFormatUtil;
import freemarker.core.TemplateValueFormatException;
import freemarker.core.UnparsableValueException;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateModelException;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

/**
 * 將西元年轉為民國年
 * <p>
 * 使用方式 ${aDate?string.@mingguo}, 會將 2020/07/16 轉為 109/07/16
 */
public class MingGuoTemplateDateFormatFactory extends TemplateDateFormatFactory {

    public static final MingGuoTemplateDateFormatFactory INSTANCE = new MingGuoTemplateDateFormatFactory();

    private MingGuoTemplateDateFormatFactory() {
        // Defined to decrease visibility
    }

    @Override
    public TemplateDateFormat get(String s, int i, Locale locale,
                                  TimeZone timeZone, boolean b, Environment environment)
            throws TemplateValueFormatException {
        TemplateFormatUtil.checkHasNoParameters(s);
        return MingGuoTemplateDateFormat.INSTANCE;
    }

    private static class MingGuoTemplateDateFormat extends TemplateDateFormat {

        public static final MingGuoTemplateDateFormat INSTANCE = new MingGuoTemplateDateFormat();

        private MingGuoTemplateDateFormat() {
            // Defined to decrease visibility
        }

        @Override
        public String formatToPlainText(TemplateDateModel templateDateModel) throws TemplateModelException {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
            Date date = new Date(TemplateFormatUtil.getNonNullDate(templateDateModel).getTime());

            String dateStr = dateFormat.format(date);
            return (Integer.parseInt(dateStr.substring(0, 4)) - 1911) + dateStr.substring(4);
        }

        @Override
        public Object parse(String s, int i) throws TemplateValueFormatException {
            try {
                return new Date(Long.parseLong(s));
            } catch (NumberFormatException e) {
                throw new UnparsableValueException("Malformed long");
            }
        }

        @Override
        public boolean isLocaleBound() {
            return false;
        }

        @Override
        public boolean isTimeZoneBound() {
            return false;
        }

        @Override
        public String getDescription() {
            return "西元年轉民國年";
        }
    }

}

2. Register to freemarker configuration class:
    Configuration cfg = new Configuration(Configuration.getVersion());
    cfg.setClassForTemplateLoading(this.getClass(), "/");
    
    // register the "mingguo" format:
    Map<String, TemplateDateFormatFactory> customDateFormats = new HashMap<>();
    customDateFormats.put("mingguo", MingGuoTemplateDateFormatFactory.INSTANCE);

    cfg.setCustomDateFormats(customDateFormats);

3. Use this format in ftl template file:
<#-- 取得現在時間 -->
<#assign aDateTime = .now>
<#-- 只保留日期 -->
<#assign aDate = aDateTime?date>
<#-- create the macro variable: -->
<#macro printDate mingguodate>
列印日期:${mingguodate}

</#macro>
<#macro footer records amount>
${"-"?right_pad(25, "-")}
總筆數:${records}
總金額:${amount}
</#macro>
<#-- call the macro: -->
<@printDate mingguodate="${aDate?string.@mingguo}" />
<#assign total = 0>
${"水果名稱"?right_pad(10)}${"單價"?right_pad(10)}${"訂購數量"?right_pad(10)}
${"-"?right_pad(25, "-")}
<#list rows as row>
<#assign total += row.price * row.quantity>
${row.name?right_pad(10)}${row.price?string.@ntd?left_pad(10)}${row.quantity?left_pad(10)}
</#list>
<#-- call the macro: -->
<@footer records="${rows?size?left_pad(10)}" amount="${total?string.@ntd?left_pad(10)}" />


Reference

2020/10/10

[Apache Freemarker] custom number format example

Purpose
FreeMarker allows you to define your own number and date/time/datetime formats, and associate a name to them.

Here has an example to demonstrate how to convert amount from 2380 to NT$2,380.


How-To
1. Create custom number format class:
package com.cht.tool.filegenerator.ftl.custom;

import freemarker.core.Environment;
import freemarker.core.TemplateFormatUtil;
import freemarker.core.TemplateNumberFormat;
import freemarker.core.TemplateNumberFormatFactory;
import freemarker.core.TemplateValueFormatException;
import freemarker.core.UnformattableValueException;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
import freemarker.template.utility.NumberUtil;

import java.text.DecimalFormat;
import java.util.Locale;

/**
 * 將金額加上千分號,並註明新台幣 NT
 * <p>
 * 使用方式 ${total?string.@ntd},2380 會被轉換為 NT$2,380
 */
public class NTDTemplateNumberFormatFactory extends TemplateNumberFormatFactory {

    public static final NTDTemplateNumberFormatFactory INSTANCE = new NTDTemplateNumberFormatFactory();
    private static DecimalFormat ntdFormat = new DecimalFormat("NT$#,###");

    private NTDTemplateNumberFormatFactory() {
        // Defined to decrease visibility
    }

    @Override
    public TemplateNumberFormat get(String s, Locale locale, Environment environment)
            throws TemplateValueFormatException {
        TemplateFormatUtil.checkHasNoParameters(s);
        return NTDTemplateStringFormat.INSTANCE;
    }

    private static class NTDTemplateStringFormat extends TemplateNumberFormat {

        private static final NTDTemplateStringFormat INSTANCE = new NTDTemplateStringFormat();

        private NTDTemplateStringFormat() {
            // Defined to decrease visibility
        }

        /**
         * 2380 會被轉換為 NT$2,380 的邏輯寫在這裡
         *
         * @param numberModel
         * @return
         * @throws TemplateValueFormatException
         * @throws TemplateModelException
         */
        @Override
        public String formatToPlainText(TemplateNumberModel numberModel)
                throws TemplateValueFormatException, TemplateModelException {
            Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
            try {
                return ntdFormat.format(NumberUtil.toIntExact(number));
            } catch (RuntimeException e) {
                throw new UnformattableValueException(number + " - 金額轉換失敗", e);
            }
        }

        @Override
        public boolean isLocaleBound() {
            return false;
        }

        @Override
        public String getDescription() {
            return "數字轉新台幣";
        }
    }
}

2. Register to freemarker configuration class:
    Configuration cfg = new Configuration(Configuration.getVersion());
    cfg.setClassForTemplateLoading(this.getClass(), "/");
    
    // register the "ntd" format:
    Map<String, TemplateNumberFormatFactory> customNumberFormats = new HashMap<>();
    customNumberFormats.put("ntd", NTDTemplateNumberFormatFactory.INSTANCE);
    
    cfg.setCustomNumberFormats(customNumberFormats);

3. Use this format in ftl template file:
<#-- 取得現在時間 -->
<#assign aDateTime = .now>
<#-- 只保留日期 -->
<#assign aDate = aDateTime?date>
<#-- create the macro variable: -->
<#macro printDate mingguodate>
列印日期:${mingguodate}

</#macro>
<#macro footer records amount>
${"-"?right_pad(25, "-")}
總筆數:${records}
總金額:${amount}
</#macro>
<#-- call the macro: -->
<@printDate mingguodate="${aDate?string.@mingguo}" />
<#assign total = 0>
${"水果名稱"?right_pad(10)}${"單價"?right_pad(10)}${"訂購數量"?right_pad(10)}
${"-"?right_pad(25, "-")}
<#list rows as row>
<#assign total += row.price * row.quantity>
${row.name?right_pad(10)}${row.price?string.@ntd?left_pad(10)}${row.quantity?left_pad(10)}
</#list>
<#-- call the macro: -->
<@footer records="${rows?size?left_pad(10)}" amount="${total?string.@ntd?left_pad(10)}" />


Reference