ClickとS2の連携

Springとの連携でnet.sf.click.extras.spring.SpringClickServletってのがあるからこれを参考すればできそうな予感がする。


必要なjarを追加して、

aopalliance-1.0.jar
commons-logging-1.0.4.jar
geronimo-jta_1.0.1B_spec-1.0.jar
hsqldb.jar
javassist-3.0.jar
junit-3.8.1.jar
log4j-1.2.8.jar
ognl-2.6.5.jar
poi-2.5.1-final-20040804.jar
s2-framework-2.3.6.jar
s2-extension-2.3.6.jar


そしてS2ClickServletの作成

package sample.extras.seasar;

import net.sf.click.ClickServlet;
import net.sf.click.Page;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

public class S2ClickServlet extends ClickServlet {

    protected Page newPageInstance(String path, Class pageClass) throws Exception {
        S2Container container = SingletonS2ContainerFactory.getContainer();
        return (Page) container.getComponent(pageClass);
    }

}

単純だけど、こんな感じでどうにかなるかなー。


とりあえず動かすためにweb.xmlをこんな感じで修正して

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
   
 <display-name>Sample Click</display-name>
 <description>Sample Click application.</description>

 <filter>
  <filter-name>s2filter</filter-name>
  <filter-class>org.seasar.framework.container.filter.S2ContainerFilter</filter-class>
 </filter>

 <filter-mapping>
  <filter-name>s2filter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>

 <servlet>
  <servlet-name>s2container</servlet-name>
  <servlet-class>org.seasar.framework.container.servlet.S2ContainerServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
 </servlet>
  
 <servlet>
  <servlet-name>s2click-servlet</servlet-name>
  <servlet-class>sample.extras.seasar.S2ClickServlet</servlet-class>
  <load-on-startup>2</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>s2container</servlet-name>
  <url-pattern>/s2container</url-pattern>
 </servlet-mapping>

 <servlet-mapping>
  <servlet-name>s2click-servlet</servlet-name>
  <url-pattern>*.htm</url-pattern>
 </servlet-mapping>

 <welcome-file-list>
  <welcome-file>redirect.html</welcome-file>
 </welcome-file-list>
 
</web-app>

aop.dicon、app.dicon、j2ee.dicon、log4j.propertiesとついでにallaop.diconをコピーして
sample.diconを新規に作成

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
	"http://www.seasar.org/dtd/components21.dtd">
<components>
	<include path="sample/dicon/allaop.dicon"/>

	<!-- auto registration -->
	<component class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister">
		<property name="instanceDef">
			@org.seasar.framework.container.deployer.InstanceDefFactory@SINGLETON
		</property>
		<property name="autoNaming">
			<component class="org.seasar.framework.container.autoregister.DefaultAutoNaming"/>
		</property>
		<initMethod name="addClassPattern">
			<arg>"sample.service.impl"</arg>
			<arg>".*ServiceImpl"</arg>
		</initMethod>
	</component>
	<component class="org.seasar.framework.container.autoregister.AspectAutoRegister">
		<property name="interceptor">logicInterceptorChain</property>
		<initMethod name="addClassPattern">
			<arg>"sample.service.impl"</arg>
			<arg>".*ServiceImpl"</arg>
		</initMethod>
	</component>
	
	<component class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister">
		<property name="instanceDef">
			@org.seasar.framework.container.deployer.InstanceDefFactory@REQUEST
		</property>
		<property name="autoNaming">
			<component class="org.seasar.framework.container.autoregister.DefaultAutoNaming"/>
		</property>
		<initMethod name="addClassPattern">
			<arg>"sample.page"</arg>
			<arg>".*"</arg>
		</initMethod>
	</component>
	<component class="org.seasar.framework.container.autoregister.AspectAutoRegister">
		<property name="interceptor">pageInterceptorChain</property>
		<initMethod name="addClassPattern">
			<arg>"sample.page"</arg>
			<arg>".*"</arg>
		</initMethod>
	</component>
	
</components>

あと忘れないようにapp.diconにincludeを追加して

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
	"http://www.seasar.org/dtd/components21.dtd">
<components>
	<include path="sample/dicon/sample.dicon"/>
</components>

LoginクラスにLoginServiceをインジェクションするように変更。

package sample.page;

import net.sf.click.Page;
import net.sf.click.control.Form;
import net.sf.click.control.PasswordField;
import net.sf.click.control.Submit;
import net.sf.click.control.TextField;
import sample.dto.UserDto;
import sample.service.LoginService;

public class Login extends Page {

    private Form form = new Form("form");
    
    private LoginService loginService;

    public void setLoginService(LoginService loginService) {
    	this.loginService = loginService;
    }
    
    public Login() { 
        form.add(new TextField("username", true));
        form.add(new PasswordField("password", true));
        form.add(new Submit("ok", "   OK   ", this, "onOkClicked"));
        addControl(form);
    }

    public boolean onOkClicked() {
        if (form.isValid()) {
            UserDto user = new UserDto();
            form.copyTo(user);

            if (loginService.execute(user)) {
                getContext().setSessionAttribute("user", user);
                setRedirect("secure.htm");
            } else {     
                form.setError(getMessage("authentication-error"));
            }
        }
        return true;
    }

}

これで動くはず!!!Tomcatを起動して確認。





一応うごいたけど、、、なんか変。。。
おかしなところは、

  • Loginクラス(Pageクラス)のtraceが出力されていない。アスペクトがうまくいってないのかなー。
  • LoginクラスのgetMessage()でsample.page.Login$$EnhancedByS2AOP$$2cf63のリソースファイルがないからエラーとなる。
  • sample.page.Loginのプロパティ(headers)が見つからないので設定をスキップしますっていうtraceが出力されてる


traceが出力されないのはなぜだろう。。。わからない。。。
Serviceのほうはうまく動いてるんだよねー。sample.diconに違いなんてほとんどないのに。
sample.page.Login$$EnhancedByS2AOP$$2cf63とか表示されてるから一応動いてそうな予感だするんだけど、、、
ためしにpointcutでon〜系のメソッドのみを対象とするように変更して

	<component class="org.seasar.framework.container.autoregister.AspectAutoRegister">
		<property name="interceptor">pageInterceptorChain</property>
		<property name="pointcut">"on.*"</property>
		<initMethod name="addClassPattern">
			<arg>"sample.page"</arg>
			<arg>".*"</arg>
		</initMethod>
	</component>

再度実行。

おー。きちんと出力されるようになった!!!なにが原因かよくわかんけど、、、Pageクラスにメソッドが多すぎるから!?

[追記]
デフォルトではinterfaceのメソッドにのみ適用されるから。interfaceを実装していない場合はきちんとpointcutを指定する必要がある。dさん、コメントありがとー




次はリソースファイルが見つからない問題だけど、これはもう独自の基底Pageクラスを作って対応しよう。

package sample.page;

import java.util.Locale;
import java.util.Map;

import net.sf.click.Page;
import net.sf.click.util.MessagesMap;

public class S2Page extends Page {
	
    public Map getMessages() {
        if (messages == null) {
            if (getContext() != null) {
                String baseName = getClass().getName();
                if (baseName.matches(".*\\$EnhancedByS2AOP\\$.*")) {
                	baseName = getClass().getSuperclass().getName(); 
                }
                Locale locale = getContext().getLocale();
                messages = new MessagesMap(baseName, locale);

            } else {
                String msg = "Context not set cannot initialize messages";
                throw new IllegalStateException(msg);
            }
        }
        return messages;
    }

}

常に親クラス名を指定しても問題ないなーと思ったけど、なんとなくこうしてみた。

[追記]
これはインターセプタのgetTargetClass(invocation)を利用したほうがよさそう。
だいぶ前にきむきむさんに教えてもらって、それから試したけど、今はソースが見つからない。。。整理したときに削除したのかも




最後にsample.page.Loginのプロパティ(headers)が見つからないので設定をスキップしますっていうtraceが出力されてるってことについてなんだけど、確かにPageクラスに Map setHeaders(Map m)ってのが定義されている。。。まぁログが出力されるだけだからそこまで問題ではないんだけど。
親クラスのセッターに対してはインジェクションしないとかインジェクションの対象とはしないって設定はどうすればできるんだろう。。。



Bindingアノテーションを使えばよさそうだなー。
さっき作ったS2Pageクラスに

	public static final String headers_BINDING = "bindingType=none";

の定数アノテーションを追加。
これでスキップしますって出力されなくなった。


S2Pageクラスを作った点は気になるところだけど一応これで連携はできた。
やっぱりS2Pageクラスはなくしたいなー。まっっなんかいい案を思いついたときに対応しよう。
次は簡単なWebアプリケーションを作りたいなー。