InitFilePreparator.java

package io.extact.rms.application.persistence.file.init;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.stream.StreamSupport;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;

import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.microprofile.config.Config;

import lombok.extern.slf4j.Slf4j;

import io.extact.rms.application.persistence.GenericRepository.ApiType;
import io.extact.rms.application.persistence.file.IoSystemException;
import io.extact.rms.application.persistence.file.io.FileAccessor;
import io.extact.rms.application.persistence.file.io.PathResolver;

@ApplicationScoped
@Slf4j
public class InitFilePreparator {

    private Config config;

    @Inject
    public InitFilePreparator(Config config) {
        this.config = config;
    }

    /**
     * CDIコンテナのApplicationScopedなBeanの準備完了イベントハンドラ
     * <p>
     * @param event イベント
     */
    public void onApplicationScopedInitializedEvent(@Observes @Initialized(ApplicationScoped.class) Object event) {
        if (config.getValue(ApiType.PROP_NAME, String.class).equals(ApiType.FILE)) {
            initDataIfAbsent();
        }
    }

    /**
     * fileTypeがpermanentだがマスタデータ格納フォルダが存在しない場合に初期データを投入する。
     * <code>csv.type.permanent.init.data</code>で初期データフォルダが指定されている場合はそのデータを投入、
     * 指定されていない場合は<code>csv.temporary.fileName</code>に指定されているリソースファイルを投入する。
     */
    private void initDataIfAbsent() {

        if (config.getValue("csv.type", String.class).equals("temporary")) {
            return;
        }

        PathResolver pathEnv = new PathResolver.FixedDirPathResolver();
        if (Files.exists(pathEnv.getBaseDir())) {
            return;
        }

        String initDataPath = config.getOptionalValue("csv.type.permanent.init.data", String.class).orElse(null);
        if (initDataPath != null) {
            var from = Paths.get(initDataPath);
            RecursiveCopyCommand.from(from).to(pathEnv.getBaseDir()).copy();
            log.info("初期データを作成しました。" + from + "=>" + pathEnv.getBaseDir());
            return;
        }

        // csv.temporary.fileName.$1 のvalueに指定されているリソースファイルを読み込み
        // csv.permanent.fileName.$1 のvalueのファイル名でデータ格納フォルダに出力する
        String temporaryFileConfigPrefix = "csv.temporary.fileName";
        StreamSupport.stream(config.getPropertyNames().spliterator(), false)
            .filter(configKey -> configKey.startsWith(temporaryFileConfigPrefix))
            .map(configKey -> {
                var fileNameInConfigKey = configKey.substring((temporaryFileConfigPrefix + ".").length()); // csv.temporary.fileName.$1の$1部分を取得
                var resoucePath = config.getValue(configKey, String.class);
                var outputFileName = config.getValue("csv.permanent.fileName." + fileNameInConfigKey, String.class);
                return Pair.of(resoucePath, outputFileName);
            })
            .forEach(fileNames ->
                FileAccessor.copyResourceToRealPath(fileNames.getLeft(), pathEnv, fileNames.getRight())
            );
        log.info("初期データを作成しました。${csv.type.temporary.fileName}" + "=>" + pathEnv.getBaseDir());
    }

    /**
     * ディレクトリをfromからtoにコピーする。
     * サブディレクトリの中身も全てコピーする<br>
     * コピー先が存在する場合は例外を送出する<br>
     */
     static class RecursiveCopyCommand {

        private Path from;
        private Path to;

        private RecursiveCopyCommand() {
            // nop
        }

        public static RecursiveCopyCommand from(Path from) {
            var command = new RecursiveCopyCommand();
            command.from = from;
            return command;
        }
        public RecursiveCopyCommandFinsher to(Path to) {
            this.to = to;
            return new RecursiveCopyCommandFinsher();
        }

        public class RecursiveCopyCommandFinsher {

            public void copy() {
                //コピー元
                final Path fromPath = from;
                //コピー先
                final Path toPath = to;

                //FileVisitorの定義
                FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        //ディレクトリをコピーする
                        Files.copy(dir, toPath.resolve(fromPath.relativize(dir)), StandardCopyOption.COPY_ATTRIBUTES);
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        //ファイルをコピーする
                        Files.copy(file, toPath.resolve(fromPath.relativize(file)), StandardCopyOption.COPY_ATTRIBUTES);
                        return FileVisitResult.CONTINUE;
                    }
                };

                //ファイルツリーを辿ってFileVisitorの動作をさせる
                try {
                    Files.walkFileTree(fromPath, visitor);
                } catch (IOException e) {
                    throw new IoSystemException(e);
                }
            }
        }
    }
}