java.net.URLConnectionのguessContentTypeFromName
は、content.types.user.table
というシステムプロパティで定義された内容で任意のMIME typeを推測できるようになります。
公式ドキュメントにも書かれています。
URLConnection (Java Platform SE 8)
では実際にそれを確かめてみようと、テストコードの中でSystem.setProperty("content.types.user.table", "/path/to/content-types.properties")
と指定してもさっぱり有効になりませんでした。
例えば、拡張子.css
をtext/css
と判定させるためには、以下のような設定ファイルを用意します。
text/css: \ description=Cascading Style Sheets;\ file_extensions=.css
以下のようなコードを用意して、設定ファイルを読み込んでみましが、mimeType
にはnullが入りました。
System.setProperty("content.types.user.table", "/path/to/content-types.properties") val mimeType = URLConnection.guessContentTypeFromName("test.css")
実行時ではなく、予めsbt -Dcontent.types.user.table=/path/to/content-types.properties
というふうに実行前に設定されるようにしておくと意図した通りにmimeType
にはtext/css
が入ります。
java.net.URLConnectionのguessContentTypeFromNameのコードを追いかける
これは何が起きているのでしょうか?
実は最初、Stack OverflowにSystem.setProperty
でセットすれば反映されると書かれていたので、それを鵜呑みにして「正しく動かない!自分の書いたコードがおかしいのか?」と思いましたが、実際にはその回答が誤りでした。
順番に、java.net.URLConnection
のguessContentTypeFromName
メソッドの挙動を、openJDK10のコードを例に追いかけていきましょう。
openJDKの該当するソースコードは以下の場所に有ります。
jdk10/master: be620a591379 src/java.base/share/classes/java/net/URLConnection.java
public static String guessContentTypeFromName(String fname) { return getFileNameMap().getContentTypeFor(fname); } public static FileNameMap getFileNameMap() { FileNameMap map = fileNameMap; if (map == null) { fileNameMap = map = new FileNameMap() { private FileNameMap internalMap = sun.net.www.MimeTable.loadTable(); public String getContentTypeFor(String fileName) { return internalMap.getContentTypeFor(fileName); } }; } return map; }
MIME Typeに関連するデータは、FileNameMap
というクラスに格納されていること、値が無ければ(nullならば)されていなければsun.net.www.MimeTable.loadTable
を呼び出して、初期化していることが分かります。
なお、getContentTypeFor
はFileNameMap
のメソッドで、ほぼ単純なmap構造のデータから該当するMIME typeを取得する機能を提供します。
sun.net.www.MimeTable.loadTableの中身を追いかける
では、実際にMIME typeのテーブルを保持するsun.net.www.MimeTable.loadTable
の中身を追いかけてみましょう。
sun.net.www.MimeTable.loadTable
は以下のファイルに収録されています。
jdk10/master: be620a591379 src/java.base/share/classes/sun/net/www/MimeTable.java
public static FileNameMap loadTable() { MimeTable mt = getDefaultTable(); return (FileNameMap)mt; } public static MimeTable getDefaultTable() { return DefaultInstanceHolder.defaultInstance; } private static class DefaultInstanceHolder { static final MimeTable defaultInstance = getDefaultInstance(); static MimeTable getDefaultInstance() { return java.security.AccessController.doPrivileged( new java.security.PrivilegedAction<MimeTable>() { public MimeTable run() { MimeTable instance = new MimeTable(); URLConnection.setFileNameMap(instance); return instance; } }); } }
loadTable
から始まり、DefaultInstanceHolder
クラスにdefaultInstance
というstaticなメンバが有ることが分かります。staticなクラスのstaticなメンバなので、クラス自体がロードされた時点で、getDefaultInstance
メソッドが実行され、defaultInstance
にはMIME typeのデータがロードされている、ということなのです。
更に、この時点でなぜかURLConnection.setFileNameMap
を呼び出し、ロードしたMIME Typeをセットしています。getFileNameMap
メソッドの中でやっているFileNameMap map
がnullか否かを判定するロジックっていらなくない?って思いますね。
実際にロードする部分のコードをもう少し追いかけてみましょう。
MimeTable() { load(); } public synchronized void load() { Properties entries = new Properties(); File file = null; InputStream in; // First try to load the user-specific table, if it exists String userTablePath = System.getProperty("content.types.user.table"); if (userTablePath != null && (file = new File(userTablePath)).exists()) { try { in = new FileInputStream(file); } catch (FileNotFoundException e) { System.err.println("Warning: " + file.getPath() + " mime table not found."); return; } } else { in = MimeTable.class.getResourceAsStream("content-types.properties"); if (in == null) throw new InternalError("default mime table not found"); } try (BufferedInputStream bin = new BufferedInputStream(in)) { entries.load(bin); } catch (IOException e) { System.err.println("Warning: " + e.getMessage()); } parse(entries); }
確かにcontent.types.user.table
というシステムプロパティが設定されていれば、そこからパスを取得するようになっていますね。存在しなければデフォルトのcontent-types.properties
を取得しています。
ちなみに、このcontent.types.user.table
というシステムプロパティを元に任意のMIME Typeを設定する機能について、テストコードが存在しません。確かにこれではグローバルにシステムプロパティを汚染しないとテストが書けないですね…
おわりに
以上、java.net.URLConnection
のguessContentTypeFromName
におけるcontent.types.user.table
の取扱について、実際のJavaのライブラリのコードを追いかけてみました。
依存関係(sun.net.www.MimeTable.loadTable
がjava.net.URLConnection
に依存している)がおかしいとか、初回利用時ではなく、クラスロード時に強制的に初期化が行われ、以降は再設定もできないとか、そもそもテストが無いとか、よく考えるとguessContentTypeFromName
というメソッド自体java.net.URLConnection
ではなく、独立したMIMEに関するクラスに所属しているべきでは?と、標準のJavaのライブラリでも色々と設計が気になるところが有るんだな、というのが今回の感想でした。
- 作者: 高橋麻奈
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2016/08/31
- メディア: 単行本
- この商品を含むブログを見る