ログ日記

作業ログと日記とメモ

S2WicketがWicket1.5の自動リロードonで動いた

前回*1の続き。


原因が分かった。
Wicket1.4のFilter#initは

       public void init(FilterConfig filterConfig) throws ServletException
        {
                initIgnorePaths(filterConfig);

                this.filterConfig = filterConfig;
                String filterMapping = filterConfig.getInitParameter(WicketFilter.FILTER_MAPPING_PARAM);

                if (SERVLET_PATH_HOLDER.equals(filterMapping))
                {
                        servletMode = true;
                }

                final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
                final ClassLoader newClassLoader = getClassLoader();
                try
                {
                        if (previousClassLoader != newClassLoader)
                        {
                                Thread.currentThread().setContextClassLoader(newClassLoader);
                        }

...


                        webApplicationFactory = getApplicationFactory();

                        // Construct WebApplication subclass
                        webApplication = webApplicationFactory.createApplication(this);

となっている。
setContextClassLoaderを呼び出した後にcreateApplicationでWebApplicationを生成しているのがポイント。



ところがWicket1.5 rc2では

	public void init(final boolean isServlet, final FilterConfig filterConfig)
		throws ServletException
	{
		this.filterConfig = filterConfig;

		applicationFactory = getApplicationFactory();
		application = applicationFactory.createApplication(this);
		application.setName(filterConfig.getFilterName());
		application.setWicketFilter(this);

...

		final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
		final ClassLoader newClassLoader = getClassLoader();

		ThreadContext.setApplication(application);
		try
		{
			if (previousClassLoader != newClassLoader)
			{
				Thread.currentThread().setContextClassLoader(newClassLoader);
			}

			application.initApplication();

となっている。
バグか仕様かは分からないが、WebApplicationを生成してからsetContextClassLoaderを呼び出している。
こうなると、WebApplicationはReloadingClassLoaderではなく、デフォルトのクラスローダーが使われる。
で、mountPageやトップページが動かなかったのは

    @Override
    public Class<? extends Page> getHomePage() {
        return TopPage.class;
    }

    @Override
    protected void init(){
        super.init();

        mountPage("/guest", GuestPage.class);
    }

と書いていたからで、WebApplicationがデフォルトのクラスローダーで生成されるなら、このファイルに書いたTopPageやGuestPageもデフォルトのクラスローダーで生成される。
で、ReloadingClassLoaderからもこれらのクラスが生成されるとClassCastExceptionになる。または、構造上は継承関係にあってもクラスローダーが違うと

                else if (!clazz.isAssignableFrom(container.getClass()))
                {
                        throw new WicketRuntimeException("Parameter clazz must be an instance of " +
                                container.getClass().getName() + ", but is a " + clazz.getName());
                }

ここで継承関係ではないと判断されて例外が発生する。



まぁ結局のところ、S2WicketFilterでsuper.init()を呼び出す前に自分でsetContextClassLoaderを呼び出してしまえばいい。

        String contextKey = filterConfig.getFilterName();
        WebApplication webApplication = (WebApplication) Application.get(contextKey);
        if (webApplication != null){
            // WARM DEPLOY の場合に applicationKeyToApplication.removeを呼び出すため
            webApplication.internalDestroy();
        }

        Thread.currentThread().setContextClassLoader(getClassLoader());
        super.init(isServlet, filterConfig);

あと自動リロード時にApplicationが既にあるというエラーが出るので破棄しておく。こっちはちょっと自信ない。