Javaアプリから外部コマンドを実行した際に、ファイルパスが認識されない

JavaでXML署名を実装した際にドン詰まりしたのでメモ。

##Javaアプリからkeytool -importkeystoreコマンドを実行

でもインポートされず。。。
実行時に関係するメソッドは以下の3つで、importProcessExec ⇒ getImportCommand ⇒ processExecの順で実行される。

```java:sample.java
    /**
     * 証明書インポートコマンドを実行する.
     *
     * @param certFilePath 証明書ファイル
     * @param destAlias 識別名
     * @param keyStoreFilePath キーストアファイル
     * @param certificatePass 証明書パスワード
     * @param keyStorePass キーストアパスワード
     * @return true: 正常終了, false: 異常終了
     * @throws InterruptedException
     */
    private boolean importProcessExec(File keyStoreFile, String destAlias, File certFile,
            String certificatePass,
            String keyStorePass) throws InterruptedException {
        // 証明書のインポートを行うコマンド
        List<String> command = getImportCommand(keyStoreFile, destAlias, certFile, certificatePass,
                keyStorePass);
        // インポートコマンド実行
        return processExec(command);
    }

    /**
     * 証明書インポートコマンドを取得する.
     *
     * @param certFilePath 証明書ファイル
     * @param destAlias 識別名
     * @param keyStoreFilePath キーストアファイル
     * @param certificatePass 証明書パスワード
     * @param keyStorePass キーストアパスワード
     * @return resultList インポートコマンド
     */
    private List<String> getImportCommand(File keyStoreFile, String destAlias, File certFile,
            String certificatePass, String keyStorePass) {
        // 証明書のインポートを行うコマンド
        String command = "keytool -importkeystore -keystore %keyStoreFilePath% -srckeystore %certFilePath% -srcstoretype PKCS12 -srcalias 1 -destalias %destalias% -srcstorepass %certificatePass% -deststorepass %keyStorePass%";

        // コマンドを文字列配列に分割
        String[] commandList = command.split(" ");

        List<String> resultList = new ArrayList<String>();

        for (String cmd : commandList) {
            switch (cmd) {
            case "%keyStoreFilePath%":
                cmd = cmd.replace("%keyStoreFilePath%", includeDoubleQuotes(keyStoreFile.getPath()));
                break;
            case "%certFilePath%":
                cmd = cmd.replace("%certFilePath%", includeDoubleQuotes(certFile.getPath()));
                break;
            case "%destalias%":
                cmd = cmd.replace("%destalias%", destAlias);
                break;
            case "%certificatePass%":
                cmd = cmd.replace("%certificatePass%", certificatePass);
                break;
            case "%keyStorePass%":
                cmd = cmd.replace("%keyStorePass%", keyStorePass);
                break;
            }
            resultList.add(cmd);
        }

        return resultList;
    }

    /**
     * 外部プロセスを実行する.
     *
     * @param command コマンド内容
     * @return true: 正常終了, false: 異常終了
     */
    private boolean processExec(List<String> command) {
        // 処理結果
        boolean result = false;

        try {
            ProcessBuilder processBuilder = new ProcessBuilder(command);
            Process Process = processBuilder.start();

            // プロセスの正常終了まで待機させる
            if (Process.waitFor() == 0) {
                result = true;
                log.info("Process Success: " + command.toString());
            } else {
                log.warn("Process Failed: " + command.toString());
            }

            // 標準出力
            String strInput;
            BufferedReader ipbr = new BufferedReader(new InputStreamReader(Process.getInputStream()));
            while((strInput = ipbr.readLine()) != null) {
                log.info(strInput);
            }
            ipbr.close();

            // エラー出力
            String strErr;
            BufferedReader erbr = new BufferedReader(new InputStreamReader(Process.getErrorStream()));
            while((strErr = erbr.readLine()) != null) {
                log.info(strErr);
            }
            erbr.close();

            //ProcessBuilderを使用後、バックグラウンドでInputStream, OutputStream, ErrorStreamがオープンされる.
            // リソース不足を回避するため全てのストリームをクローズする.
            Process.getInputStream().close();
            Process.getOutputStream().close();
            Process.getErrorStream().close();
        } catch (InterruptedException | IOException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }

        return result;
    }

    /**
     * 文字列をダブルクォーテーションで括る.
     *
     * @param str 文字列
     * @return ダブルクォーテーションで括られた文字列
     */
    private String includeDoubleQuotes(String str) {
        return "\"" + str + "\""; 
    }
```

##【原因】証明書ファイルパスを「"」で括っていたため

メソッド：includeDoubleQuotesで、証明書ファイルパスを「"」で括っており、Javaアプリから実行されるコマンド

```
keytool -importkeystore -keystore "/.../XXXX.keystore" -srckeystore "/.../XXXX.p12" -srcstoretype PKCS12 -srcstorepass root -deststorepass changeit
```

が正常に実行されていなかったようです。
「"」を付与する関数を除外することで正しく動作しました。

開発環境がWindowsで、証明書の配置場所をProgram Files配下にしていました。
ファイルパスに半角スペースが含まれており、証明書のファイルパスを「"」で括らないと正常にコマンド実行されなかったので、関数をかませて「"」をつけるようにしていたのです。

こういう外部プロセス実行系の処理は要注意ですね。
たとえば、windowsにlinuxの仮想環境を作って、そこにデプロイして動作確認とかしないと危ないかも。。。