2015年7月19日日曜日

[メタデータエディタ]DiConfig

DIコンフィグクラス。
別掲記事にも記載があるが、今回紹介しているメタデータエディタのサンプルでは、ドメイン駆動実装を試験的に採用しているため、DIの仕組みが必要となる。前掲プログラムとほぼ同じものであるが、一応掲載した。

package di;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import util.AppLog;
import util.PropUtil;
import util.SvFile;
import exception.ApplicationInternalException;

//---------------------------------------------------------------------------//
// このクラスの使い方
// 1. DI設定のタブ区切りファイル "di.tsv" を作成します。
// 2. DI設定は次のように記述します。
//    "[DI設定するクラスの名前]"[tab]"[パッケージ]"
//    実際の設定は、こんな感じです。
//    "Service"    "service.impl"
//    "Repository"    "domain"
//    "Dao"    "dao.impl"
// 3. 上記2.のように記述した場合、"service.impl"パッケージにある
//    "Service"という文字列を含むクラスを検索し、インスタンスを作成します。
//    作成したインスタンスは"XXXService"というクラス名をキーとして、
//    本クラスのマップに保存されます。
//    クラス名が"XXXServiceImpl"だとしても、設定ファイルに"Service"
//    で登録されていれば、マップには"XXXService"で登録します。
// 4. マップに保存されたクラス全てを調べ、"setXXXService"という名前
//    を持つメソッドがあれば設定します。
// 以上のようにして、DIを行います。
// DIするために必要な命名規則は、次の通りです。
//   (1) インターフェース名、クラス名の末尾を揃える。
//        "XXXService"、"XXXRepository"、"XXXDao"などのようにDI設定
//        で検索できるよう、インターフェース名、クラス名を揃えます。
//   (2) インターフェースを用いる場合は、"XXXServiceImpl"などのように
//       DI設定のキーワードの後に"Impl"などをつけて、クラスを実装してください。
//       また、クラスを実装するパッケージは"service.impl"などインターフェース
//       とは別のパッケージとしてください。
//   (3) DI設定させたいメンバがある場合は、必ずpublicメソッドで
//       "setXXXService"というメソッドを用意してください。
//---------------------------------------------------------------------------//

/**
 * DI設定クラス
 */
public class DiConfig {

    /** シングルトンのインスタンス */
    private static DiConfig diConfig = null;

    /**
     * シングルトンのインスタンスを取得します。
     *
     * @return インスタンス
     */
    public static DiConfig getInstance() {
        if (diConfig == null) {
            diConfig = new DiConfig();
        }
        return diConfig;
    }

    /** DIマップ */
    private Map<String, Object> diMap = null;

    /**
     * 名前を指定して、DIインスタンスを取得します。
     *
     * @param name
     *            取得するDIインスタンス名
     * @return DIインスタンス
     */
    public Object getDiInstance(String name) {
        return diMap.get(name);
    }

    /**
     * コンストラクタ
     */
    private DiConfig() {
        try {
            loadDb();
            injectDependency();
        } catch (IOException e) {
            throw new ApplicationInternalException(
                    PropUtil.get("msg.err.dbLoad"));
        }
    }

    /**
     * 設定を読み込み、依存性注入を実施します。
     */
    private void injectDependency() {

        // 設定が存在しなければエラーとする
        List<List<String>> csvLineList = getSvLineList();
        if (csvLineList == null || csvLineList.size() == 0) {
            throw new ApplicationInternalException(
                    PropUtil.get("msg.err.noDiConfig"));
        }

        // DI基本パスを取得する
        String diBasePath = PropUtil.get("di.config.basePath");
        if (diBasePath == null || diBasePath.isEmpty()) {
            throw new ApplicationInternalException(
                    PropUtil.get("msg.err.noDiConfigBasePath"));
        }

        // 基本パスがjarファイルである場合は、jarファイルを参照する
        if (diBasePath.contains(".jar")) {
            injectDependencyFromJar(diBasePath, csvLineList);
            return;
        }

        // 全てのDI設定を読み込むまでループ
        diMap = new HashMap<String, Object>();
        for (List<String> csvLine : csvLineList) {

            // CSV行の設定値が2つでない場合はエラーとする
            if (csvLine.size() != 2) {
                throw new ApplicationInternalException(
                        PropUtil.get("msg.err.ngConfig"));
            }

            // DIのキーワードとDIパッケージを元に、DI設定を検索する
            searchDiSetting(diBasePath, csvLine.get(0), csvLine.get(1));
        }

        // DI設定に基づき、インジェクションを実行する
        injectByDiSetting();
    }

    /**
     * DIキーワードでDIパッケージを検索し、見つかったクラスを記憶します。
     *
     * @param diBasePath
     *            DI基本パス
     * @param diKeyword
     *            DIキーワード
     * @param diPackage
     *            DIパッケージ
     */
    private void searchDiSetting(String diBasePath, String diKeyword,
            String diPackage) {

        // DI基本パス+DIパッケージの位置にある全てのクラスファイルを取得する
        File dir = new File(diBasePath + "/" + diPackage.replaceAll("\\.", "/"));
        File[] fileList = dir.listFiles();
        if (fileList == null || fileList.length == 0) {
            return;
        }

        // DIキーワードでDIパッケージを検索し、見つかったクラスを記憶する
        for (File file : fileList) {

            // ディレクトリは処理対象外とする
            if (file.isDirectory()) {
                continue;
            }

            // ファイル名に[DIキーワード]が含まれる場合、DI設定として保存する
            if (file.getName().contains(diKeyword)) {
                try {
                    String className = file.getName().substring(0,
                            file.getName().indexOf(diKeyword))
                            + diKeyword;
                    String fullPathClassName = diPackage
                            + "."
                            + file.getName().substring(0,
                                    file.getName().indexOf(".class"));
                    Object newInstance = Class.forName(fullPathClassName)
                            .newInstance();
                    diMap.put(className, newInstance);
                } catch (Exception e) {
                    throw new ApplicationInternalException(
                            PropUtil.get("msg.err.createDiInstanceFailed"));
                }
            }
        }
    }

    /**
     * DI設定にあるパッケージを再帰的に操作し、依存性注入を実行します。
     */
    private void injectByDiSetting() {

        // DIマップ内の全てのクラスを処理するまでループ
        for (String diClassName : diMap.keySet()) {

            // インジェクション可能なものにインジェクションを行う
            for (String targetClassName : diMap.keySet()) {

                // メソッド配列を取得する
                Method[] methods = diMap.get(targetClassName).getClass()
                        .getMethods();

                // メソッドにDIできるものがあればインジェクションを行う
                for (Method method : methods) {
                    if (method.getName().equals("set" + diClassName)) {
                        try {
                            method.invoke(diMap.get(targetClassName),
                                    diMap.get(diClassName));
                        } catch (Exception e) {
                            throw new ApplicationInternalException(
                                    PropUtil.get("msg.err.setDiInstanceFailed"));
                        }
                    }
                }
            }
        }
    }

    /**
     * jarファイルからDI設定条件に合致するクラスを探しだし、依存性挿入を行います。
     *
     * @param jarFileName
     *            jarファイル名
     * @param csvLineList
     *            CSV行リスト
     */
    private void injectDependencyFromJar(String jarFileName,
            List<List<String>> csvLineList) {

        try {
            // jarファイル内の全エントリを取得する
            File file = new File(jarFileName);
            JarFile jarFile = new JarFile(file);
            Enumeration<JarEntry> jarEntries = jarFile.entries();

            // jarファイル内の全エントリから、クラスのエントリのみを抽出する
            List<String> classNameList = new ArrayList<String>();
            while (jarEntries.hasMoreElements()) {

                // jarファイル内のエントリを取得し、クラスでなければループの先頭に戻る
                JarEntry jarEntry = jarEntries.nextElement();
                if (!jarEntry.getName().endsWith(".class")) {
                    continue;
                }

                // クラス名を求める
                String className = jarEntry.getName().substring(0,
                        jarEntry.getName().lastIndexOf(".class"));
                className = className.replace("/", ".");
                classNameList.add(className);
            }

            // 全てのDI設定を読み込むまでループ
            diMap = new HashMap<String, Object>();
            for (List<String> csvLine : csvLineList) {

                // CSV行の設定値が2つでない場合はエラーとする
                if (csvLine.size() != 2) {
                    throw new ApplicationInternalException(
                            PropUtil.get("msg.err.ngConfig"));
                }

                // DIのキーワードとDIパッケージを元に、DI設定を検索する
                searchDiSettingFromJar(file, csvLine.get(0), csvLine.get(1),
                        classNameList);
            }

            // DI設定に基づき、インジェクションを実行する
            injectByDiSetting();

        } catch (Exception e) {
            throw new ApplicationInternalException(e);
        }
    }

    /**
     * jarファイル内からDI設定に合致するクラスを探し、DIマップを作成します。
     *
     * @param file
     *            jarファイル
     * @param diKeyword
     *            DIキーワード
     * @param diPackage
     *            DIパッケージ
     * @param classNameList
     *            クラス名リスト
     */
    private void searchDiSettingFromJar(File file, String diKeyword,
            String diPackage, List<String> classNameList) {

        try {
            // jarファイルのURLを取得する
            URL[] urls = new URL[] { file.toURI().toURL() };

            // クラスローダを作成する
            ClassLoader classLoader = URLClassLoader.newInstance(urls);

            // クラス名リストを検索し、DIキーワードに合致するクラスを探し出す
            for (String className : classNameList) {

                // DIキーワードが見つからない場合は、ループの先頭に戻る
                if (!className.contains(diKeyword)) {
                    continue;
                }

                // 当該クラスがDIパッケージで示されるパッケージにない場合は、ループの先頭に戻る
                if (!className.contains(diPackage)) {
                    continue;
                }

                // 名前からクラスを取得する
                Class<?> cls = Class.forName(className, true, classLoader);

                // クラスのインスタンスを取得する
                Object newInstance = cls.newInstance();

                // DIマップに作成したインスタンスを追加する
                String diClassName = className.substring(className
                        .lastIndexOf(".") + 1);
                diClassName = diClassName.substring(0,
                        diClassName.indexOf(diKeyword))
                        + diKeyword;
                diMap.put(diClassName, newInstance);

                // TODO 消す
                // +
                AppLog.getInstance().debug(
                        "diClassName : " + diClassName + " newInstance : "
                                + newInstance.getClass().getName());
                // -

            }

        } catch (Exception e) {
            throw new ApplicationInternalException(e);
        }
    }

    /** データベース */
    private SvFile db = null;

    /**
     * データベースのインスタンスを取得します。
     *
     * @return
     * @throws IOException
     */
    private SvFile getDb() throws IOException {
        if (db == null) {
            db = new SvFile();
        }
        return db;
    }

    /**
     * SV行データリストを取得します。
     *
     * @return 行データリスト
     */
    private List<List<String>> getSvLineList() {

        // SV行データリストを取得する
        List<List<String>> svLineList;
        try {
            svLineList = getDb().getSvLineList();
        } catch (IOException e) {
            throw new ApplicationInternalException(
                    PropUtil.get("msg.err.dbRef"));
        }

        // SV行データリストを呼び出し側に戻す
        return svLineList;
    }

    /**
     * データベースからデータをロードします。
     *
     * @throws IOException
     */
    private void loadDb() throws IOException {

        // ファイルを開いてみる
        String fileName = PropUtil.get("di.config.fileName");
        File f = new File(fileName);

        // ファイルが存在しない場合は新規作成する
        if (!f.exists()) {
            FileWriter fw = new FileWriter(f);
            fw.close();
        }

        // データベースファイルを開く
        getDb().loadFile(PropUtil.get("di.config.fileName"));
    }
}

0 件のコメント:

コメントを投稿