参考メモ/Javaでclasspath上のリソースに再帰的にアクセスするにはCodeSourceを使う

きっかけと疑問：

- `Class.getResource()` や `ClassLoader.getResourceAsStream()` はピンポイントでリソースのフルパスを指定する。
- → ファイルシステムのディレクトリのように、リソースを再帰的に読みだすのはどうすればよいか？

結論から書くと、CodeSourceを使ってclasspathのファイル・システムまたはjarファイルにアクセスすることで実現できるので、参考までにメモ。

# java.security.CodeSource

- https://docs.oracle.com/javase/jp/8/docs/api/java/security/CodeSource.html
- https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/spec/security-spec.doc3.html
  - "3.2 java.security.CodeSource" 参照

- CodeSourceは、jarファイルやファイルシステム上のクラスファイルなどのコード位置をカプセル化している。
  - `CodeSource.getLocation()` でコード位置をURLとして取得できる。
- ローカルから `java -jar xxx.jar` で起動した場合も、`*.class`のあるディレクトリをclasspathに入れて起動した場合も、いずれもURLのスキーマは `file:` となる。
- よって、fileスキーマのURLから `URL.getFile()` を取得し、ディレクトリであれば通常のファイルシステムとしてアクセスできる。
  - jarファイルであれば `java.util.jar.JarFile` を使ってjarファイルに格納されたファイルにアクセスできる。

- あるクラスが格納されているCodeSourceを取得するには、`Class.getProtectionDomain().getCodeSource()` を用いる。
  - Javaのセキュリティシステムが有効な場合にパーミッションチェックの対象となる。
  - https://docs.oracle.com/javase/jp/8/docs/api/java/security/ProtectionDomain.html
  - https://docs.oracle.com/javase/jp/8/docs/technotes/guides/security/spec/security-spec.doc4.html
      - "4 アクセス制御メカニズムとアルゴリズム" 参照

# サンプルコード

- https://github.com/msakamoto-sf/java-codesource-sample
  - Java SE 8u74(x64)にてWin10/CentOS7.2で確認
  - Linux系でビルド＆実行するときは `build_run_linux.sh` を実行、Windowsなら `build_run_win.bat` を実行。


Linuxでの実行結果：

```
===> execute testpkg.Main from filesystem
Main : file
Main : /home/msakamoto/github/msakamoto-sf/java-codesource-sample/main/
(...)
Foo : file:/home/msakamoto/github/msakamoto-sf/java-codesource-sample/libfoo/
(...)
Bar : file:/home/msakamoto/github/msakamoto-sf/java-codesource-sample/libbar/

===> execute testpkg.Main from jar
Main : file
Main : /home/msakamoto/github/msakamoto-sf/java-codesource-sample/main.jar
(...)
Foo : file:/home/msakamoto/github/msakamoto-sf/java-codesource-sample/libfoo.jar
(...)
Bar : file:/home/msakamoto/github/msakamoto-sf/java-codesource-sample/libbar.jar
```

Windowsでの実行結果：

```
"===> execute testpkg.Main from filesystem"
Main : file
Main : /C:/work/SVNWORK/github/msakamoto-sf/java-codesource-sample/main/
(...)
Foo : file:/C:/work/SVNWORK/github/msakamoto-sf/java-codesource-sample/libfoo/
(...)
Bar : file:/C:/work/SVNWORK/github/msakamoto-sf/java-codesource-sample/libbar/
(...)

"===> execute testpkg.Main from jar"
Main : file
Main : /C:/work/SVNWORK/github/msakamoto-sf/java-codesource-sample/main.jar
(...)
Foo : file:/C:/work/SVNWORK/github/msakamoto-sf/java-codesource-sample/libfoo.jar
(...)
Bar : file:/C:/work/SVNWORK/github/msakamoto-sf/java-codesource-sample/libbar.jar
(...)
```

サンプルコードでの実験結果サマリ：

- ファイルシステム上のmain, libfoo, libbarディレクトリをクラスパスに指定した時は、`CodeSource.getLocation()` がそれぞれのディレクトリを返した。
- jarファイルをクラスパスに指定した時は、`CodeSource.getLocation()` がそれぞれのファイルシステム上のjarファイル名を返した。
- いずれの場合も、Java標準のAPIを使ってファイルシステム/jarファイル内のコンテンツ一覧にアクセスできた。

# 参考URL

- JAVAセキュリティメカニズムのメモ - seraphyの日記
  - http://d.hatena.ne.jp/seraphy/20070106
- 自作Javaアプリにサンドボックスで動くアドインの仕組みを作る方法 - seraphyの日記
  - http://d.hatena.ne.jp/seraphy/20131220
- PathMatchingResourcePatternResolver (Spring Framework API 2.5)
  - http://docs.spring.io/spring-framework/docs/2.5.x/api/org/springframework/core/io/support/PathMatchingResourcePatternResolver.html
- Recursive Resource Gathering in Java - CodeProject
  - http://www.codeproject.com/Articles/512501/Recursive-Resource-Gathering-in-Java

感想：

- 普段サーバサイドのJavaを扱うことが多いため気づかないことが多いが、Java自体はクライアント・アプリケーションとしての歴史の方が長い。
- そのため、サーバサイドでは普段意識しないようなセキュリティ系の機能が、意外とキー要素になっていたりする。
- デプロイ機能やWebStartなど、サーバサイドでは普段使わないようなJavaの機能に、意外とお宝が眠ってるかもしれない。