Threadへの情報のバインド

keywords : tomcat, javax.servlet.Filter, java.lang.InheritableThreadLocal
現在作成中のアプリに、監査ログの出力と閲覧という要件があります。
通常のシステム的なログ以外に、監査用のユーザーオペレーションログを出力する必要があるそうな。ログはファイルで日付でロールしてほしいとの注文付き。

で、少し考えた。

  • ログはlog4jのDailyRollingFileAppenderを使えばOK。
  • ログフォーマットの仕様はというと、ユーザー情報が必要らしい。JSF(JSP)やJSFのManagedBean内ならExternalContextからユーザー名を取ってこれるけど、domainパッケージ内のログ出力ではNG(domainパッケージはcoreパッケージにdependしない作りにしている)。
  • 内部の各メソッドがユーザー名のバケツリレーをするというのはよろしくない。

ということで、ユーザー名の取得をServletコンテナのFilter経由で行い、ユーザー名の保持をInheritableThreadLocalで行うことにしました。

Domain Driven Design 的には、applicationレイヤのお仕事になるのかな?

Thread毎にUser名を保持するためのクラス。

ThreadLocalでなくてInheritableThreadLocalを使うのは、ユーザー名を持ったスレッドの子孫スレッドに情報を継承させるため。

public class ThreadInfo {
	/** Thread固有情報を保持するためのInheritableThreadLocal定義. */
	private static ThreadLocal threadUser = new InheritableThreadLocal() {
		protected Object initialValue() {
			return new HashMap();
		}
	};
	/**
	 * Threadもしくは先祖スレッドに紐づいたユーザー名を設定します.
	 * @param user Threadもしくは先祖スレッドに紐づいたユーザー名
	 */
	public static void setUser(String user) {
		getMap().put("user", user);
	}
	
	/**
	 * Threadもしくは先祖スレッドに紐づいたユーザー名を取得します.
	 * @return Threadに紐づいたユーザー名
	 */
	public static String getUser() {
		return (String)getMap().get("user");
	}
	
	/**
	 * Thread固有情報用Mapの取得.
	 * @return Thread固有情報用Map
	 */
	private static Map getMap() {
		return (Map)threadUser.get();
	}
}

関係ないけど、JDK1.5からjava.lang.management.ThreadInfoなんてクラスもあるんですね。

ユーザー名をThreadInfoに登録するフィルタ

public class BindUserToLocalThreadFilter implements Filter {
	/**
	 * empty impl.
	 * @see javax.servlet.Filter#destroy()
	 */
	public void destroy() {
	}

	/**
	 * ユーザー名をThreadInfoに登録するフィルタ.
	 * @see ThreadInfo
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
	 *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		String user = request.getRemoteUser();
		ThreadInfo.setUser(user);
		chain.doFilter(req, res);
	}

	/**
	 * empty impl.
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	public void init(FilterConfig arg0) throws ServletException {
	}
}

web.xml

<!-- フィルタ設定(BindUserToLocalThreadFilter Filter) -->
<filter>
	<filter-name>bindUserToLocalThreadFilter</filter-name>
	<filter-class>com.hoge.filters.BindUserToLocalThreadFilter</filter-class>
   </filter>
<!-- すべてのファイルはserviceTimeFilterを経由するように設定 -->
<filter-mapping>
	<filter-name>bindUserToLocalThreadFilter</filter-name>
	<url-pattern>*.jsf</url-pattern>
</filter-mapping>

ユーザー名が必要な箇所

String user = ThreadInfo.getUser();