なかなか進まない

せっかく認証機能をつくったのにアクセス制限がないのはもったいない。

なので、LOGONしてないのにLOGON画面以外に入ろうとしたら、LOGON画面を表示する機能を作成。


昨日勉強したInterceptorで作ろうかと思ったけど、やっぱりFilterとして実装することに決定。
これはすぐにできそうだけど、テストの作り方がわからない。



テストは後回しにしよう。余裕があったら後で作る。

Filterクラスは、AuthFilterとしよう。

public class AuthFilter implements Filter {

    private FilterConfig config;

    public void init(FilterConfig config) throws ServletException {
        this.config = config;
    }

    public void destroy() {
        config = null;
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getSession().getAttribute(getSessionName()) == null) {
            httpRequest.getRequestDispatcher(getRedirectUri()).forward(request,
                    response);
            return;
        }
        chain.doFilter(request, response);
    }

    private String getSessionName() {
        return config.getInitParameter("sessionName");
    }

    private String getRedirectUri() {
        return config.getInitParameter("redirectUri");
    }

}||<
そして、web.xmlに記述を追加。
>||
  <!-- Auth Filter -->
  <filter>
    <filter-name>authFilter</filter-name>
    <filter-class>study.xxx.extention.filter.AuthFilter</filter-class>
    <init-param>
      <param-name>sessionName</param-name>
      <param-value>logonUser</param-value>
    </init-param>
    <init-param>
      <param-name>redirectUri</param-name>
      <param-value>/do/logon</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>authFilter</filter-name>
    <url-pattern>/do/site/*</url-pattern>
  </filter-mapping>

Tomcatを起動して手動による動作確認。

SiteMeshによこどりされてる。ridirectされてないせいだ。
AuthFilterでredirectするように修正。

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getSession().getAttribute(getSessionName()) == null) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            String uri = httpRequest.getContextPath() + getRedirectUri();
            httpResponse.sendRedirect(httpResponse.encodeRedirectURL(uri));
            return;
        }
        chain.doFilter(request, response);
    }

こんな感じで。

動作確認OK。


作ってて思ったけどこのFilterは、session内に指定されたオブジェクトがあるかを判断することが目的になってるから、ValidateSessionFilterとクラス名を変えよう。

修正後、動作確認。うまく動いてる。



次は、最大の課題struts-config.xmlの内容を??アノテーションによる定義にかえる作業。

RequestProcessorクラスを見る。
ActionConfig、ActionMappingを見る。
ModuleConfigImplを見るが挫折。Interfaceになっているくらいだから、こいつをオリジナルに変えてやればいいのかも。でも挫折。
ActionServletを見る。


なんとなく見えてきたきがする。
どこかでActionConfigを無理やり作って、ModuleConfig#addActionConfig()で追加してやればOK。

どこでするかは・・・。
PlugInなんかな・・・やっぱり。


どういうふうな??アノテーション規則にするか決めないと

案1

public interface AuthAction {
    
    public static final String ACTION_PATH = "/auth";
    
    public static final String ACTION_NAME = "logonForm";
    
    public static final String ACTION_SCOPE = "request";
    
    public static final String ACTION_UNKNOWN = "false";

    public static final String ACTION_VALIDATE = "false";
    
    public static final String logon_VALIDATE = "logonError";
    
    public String logon();
    
    public String logoff();
    
}

こんなに書くのはメンドくさい。

案2

public interface AuthAction {
    
    public static final String ACTION = "path=/auth, name=logonForm, scope=request, unknown=false, validate=false";
    
    public static final String logon_VALIDATE = "logonError";
    
    public String logon();
    
    public String logoff();
    
}

S2DaoのIDにシーケンスを指定する部分を真似てみた。

    public static final String id_ID = "sequence, sequenceName=SEQ_PARTY";

これは結構ありかも。

案3

public interface AuthAction {

    public static final String[] ACTION = { "path=/auth", "name=logonForm",
            "scope=request", "unknown=false", "validate=false" };

    public static final String logon_VALIDATE = "logonError";

    public String logon();

    public String logoff();

}

これもいいような気がする。処理もしやすそうだし。

簡単に実装できそうってことで、「案3」を採用。


forwardは

    public static final String[] forward名_FORWARD = { "path=/do/logon", "ridirect=true" };

という感じにしよう。


AnnotationActionConfigMapperというクラスを作ろう。

まずは、テストクラスの作成。

public class AnnotationActionConfigMapperTest extends TestCase {

    private AnnotationActionConfigMapper mapper = new AnnotationActionConfigMapper();

    public void testMappingActionConfig() {
        ActionMapping mapping = mapper.getActionConfig(AnnotationAction.class);

        assertEquals("/test", mapping.getPath());
        assertEquals("study.xxx.extention.struts.config.AnnotationAction",
                mapping.getType());
        assertEquals("testForm", mapping.getName());
        assertEquals("request", mapping.getScope());
        assertEquals(false, mapping.getUnknown());
        assertEquals(false, mapping.getValidate());
    }

}

テスト実行。エラー NullPointerExcepiton。
グリーンバーにする前に失敗にするために空のActionMappingを返却するようにAnnotationActionConfigMapperを修正。

失敗になった。次はグリーンバーにする作業に入る。


AnnotationActionConfigMapperを作ってみた。Interceptorを作ったときに覚えたことのみを利用して作成。
1つ作り方を覚えてしまうともっといい作り方を覚えなくなってしまう。困った。

public class AnnotationActionConfigMapper {

    private static final String ACTION = "ACTION";

    public ActionConfig getActionConfig(Class clazz) {
        BeanDesc beanDesc = new BeanDescImpl(clazz);

        if (!beanDesc.hasField(ACTION)) {
            return null;
        }
        Field field = beanDesc.getField(ACTION);
        String[] actionArray = (String[]) FieldUtil.get(field, null);
        
        Map actionMap = toMap(actionArray); 

        return createActionMapping(actionMap);
    }
    
    private Map toMap(String[] array) {
        Map result = new HashMap();
        for (int i = 0; i < array.length; i++) {
            StringTokenizer st = new StringTokenizer(array[i], "=");
            
            String key = "";
            String value = "";
            if (st.hasMoreTokens()) {
                key = st.nextToken();
            } else {
                throw new RuntimeException("ACTION annotation illegal format.");
            }
            
            if (st.hasMoreTokens()) {
                value = st.nextToken();
            }
            result.put(key, value);
        }
        return result;
    }
    
    private ActionMapping createActionMapping(Map map) {
        ActionMapping result = new ActionMapping();
        BeanDesc beanDesc = new BeanDescImpl(ActionMapping.class);
        
        for (Iterator it = map.keySet().iterator(); it.hasNext(); ) {
            String key = (String) it.next();
            PropertyDesc propertyDesc = beanDesc.getPropertyDesc(key);
            if (propertyDesc != null && propertyDesc.hasWriteMethod()) {
                propertyDesc.setValue(result, map.get(key));
            }
        }
        
        return result; 
    }

}

AnnotationActionConfigMapper#toMap()はあやしいけど、こんな感じでテストはとおると思う。


失敗だった。
ActionConfig#getType()で失敗してる。
そういえば設定してなかった。

ちょっと変更して、再度テスト実行。

    private ActionMapping createActionMapping(Class clazz, Map map) {
        ActionMapping result = new ActionMapping();
        result.setType(clazz.getName());
        
        BeanDesc beanDesc = new BeanDescImpl(ActionMapping.class);
        for (Iterator it = map.keySet().iterator(); it.hasNext(); ) {
            String key = (String) it.next();
            PropertyDesc propertyDesc = beanDesc.getPropertyDesc(key);
            if (propertyDesc != null && propertyDesc.hasWriteMethod()) {
                propertyDesc.setValue(result, map.get(key));
            }
        }
        
        return result; 
    }


グリーンバー。

次は、ForwardConfigを取得できるかのテスト

    public void testMappingForwardConfigSize() {
        ActionConfig mapping = mapper.getActionConfig(AnnotationAction.class);
        ForwardConfig[] forwards = mapping.findForwardConfigs();
        
        assertEquals(2, forwards.length);
    }
    
    public void testMappingForwardConfig() {
        ActionConfig mapping = mapper.getActionConfig(AnnotationAction.class);
        ForwardConfig forward = mapping.findForwardConfig("success");
        
        assertEquals("success", forward.getName());
        assertEquals("/pages/success.jsp", forward.getPath());
        assertEquals(false, forward.getRedirect());
    }

AnnotationActionには定義を追加

    public static final String[] success_FORWARD = { "path=/pages/success.jsp" };

    public static final String[] error_FORWARD = { "path=/pages/error.jsp",
            "redirect=true" };

テスト実行。いっきに2つのテストを追加したから、エラー1件、失敗1件となった。

エラーを失敗に変更する作業は抜かして、いっきにグリーンバーにしよう。



BeanDescから全フィールドを取得できない・・・・
うーん。困った。
どうしよう。


うーん。


考えても仕方ないからClass#getFields()利用しようっと。

public class AnnotationActionConfigMapper {

    private static final String ACTION = "ACTION";
    
    private static final String FORWARD_SUFFIX = "_FORWARD";

    public ActionConfig getActionConfig(Class clazz) {
        BeanDesc beanDesc = new BeanDescImpl(clazz);

        if (!beanDesc.hasField(ACTION)) {
            return null;
        }
        Field field = beanDesc.getField(ACTION);
        String[] actionArray = (String[]) FieldUtil.get(field, null);

        Map actionMap = toMap(actionArray);
        ActionConfig result = createActionMapping(clazz, actionMap);
        
        List forwards = createForwardConfigs(clazz);
        for (Iterator it = forwards.iterator(); it.hasNext(); ) {
            result.addForwardConfig((ForwardConfig) it.next());
        }
        
        return result;
    }

    private Map toMap(String[] array) {
        Map result = new HashMap();
        for (int i = 0; i < array.length; i++) {
            StringTokenizer st = new StringTokenizer(array[i], "=");

            String key = "";
            String value = "";
            if (st.hasMoreTokens()) {
                key = st.nextToken();
            } else {
                throw new RuntimeException("ACTION annotation illegal format.");
            }

            if (st.hasMoreTokens()) {
                value = st.nextToken();
            }
            result.put(key, value);
        }
        return result;
    }

    private ActionMapping createActionMapping(Class clazz, Map map) {
        ActionMapping result = new ActionMapping();
        result.setType(clazz.getName());

        BeanDesc beanDesc = new BeanDescImpl(ActionMapping.class);
        for (Iterator it = map.keySet().iterator(); it.hasNext();) {
            String key = (String) it.next();
            PropertyDesc propertyDesc = beanDesc.getPropertyDesc(key);
            if (propertyDesc != null && propertyDesc.hasWriteMethod()) {
                propertyDesc.setValue(result, map.get(key));
            }
        }

        return result;
    }

    private List createForwardConfigs(Class clazz) {
        List result = new ArrayList();
        
        Field[] fields = clazz.getFields();
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getName().endsWith(FORWARD_SUFFIX)) {
                String name = fields[i].getName().replaceAll(FORWARD_SUFFIX, "");
                
                String[] forwardArray = (String[]) FieldUtil.get(fields[i], null);
                Map forwardMap = toMap(forwardArray);
                ActionForward forward = createActionForward(name, forwardMap);
                result.add(forward);
            }
        }
        
        
        return result;
    }

    private ActionForward createActionForward(String name, Map map) {
        ActionForward result = new ActionForward();
        result.setName(name);
        
        BeanDesc beanDesc = new BeanDescImpl(ActionForward.class);
        for (Iterator it = map.keySet().iterator(); it.hasNext();) {
            String key = (String) it.next();
            PropertyDesc propertyDesc = beanDesc.getPropertyDesc(key);
            if (propertyDesc != null && propertyDesc.hasWriteMethod()) {
                propertyDesc.setValue(result, map.get(key));
            }
        }
        
        return result;
    }

}


いちおうできた・・・。

テストもとおった。
だけどなんか変。似たような処理がたくさんある。あとで修正しよう。


次は複数のActionConfigリストを返す処理。

    public void testCreateActionConfigs() {
        List mappings = mapper.getActionConfigs();
        assertEquals(2, mappings.size());
    }

テスト用のActionをもう1つ追加して、テストはこんな感じだと思うけど・・・



どうやってActionクラスのリストを取得するんだろう?
S2Containerから全コンポーネントを取得して、ACTIONアノテーションがあるかを確認するしかないのかなー。
ま、とりあえず、それで実装してみるか。

    public List getActionConfigs() {
        S2Container container = SingletonS2ContainerFactory.getContainer();

        List result = new ArrayList();
        for (int i = 0; i < container.getComponentDefSize(); i++) {
            Class clazz = container.getComponentDef(i).getConcreteClass();
            ActionConfig config = getActionConfig(clazz);
            if (config != null) {
                result.add(config);
            }
        }

        return result;
    }

S2Containerを利用しているから、テストクラスをS2TestCaseから派生するように変更。
あわせてDICONファイルも作成。

<components>
    <component class="study.xxx.extention.struts.config.AnnotationAction" instance="request"/>
    <component class="study.xxx.extention.struts.config.AnnotationTwoAction" instance="request"/>
</components>


テスト実行。2になるはずが0のままで失敗。
うまくいかない。



根本的なところが間違っているのかなー。
ちょっと見てみよう。

S2Container#getChild()っていうメソッドがある。
DICONファイルの構造がそのままS2Containerに格納されるっていうイメージなのかなー。
もしかして、container.getChild(1).getComponentDefSize()ってしたら2が取れるのかも。


とれた。
なんかちょっとわかった気がする。

    public List getActionConfigs() {
        S2Container container = SingletonS2ContainerFactory.getContainer().getChild(1);

        return createActionConfigs(container, new ArrayList());
    }
    
    public List createActionConfigs(S2Container parent, List actionConfigs) {
        for (int i = 0; i < parent.getComponentDefSize(); i++) {
            Class clazz = parent.getComponentDef(i).getConcreteClass();
            ActionConfig config = getActionConfig(clazz);
            if (config != null) {
                actionConfigs.add(config);
            }
        }
        
        if (parent.getChildSize() == 0) {
            return actionConfigs;
        }
        
        for (int i = 0; i < parent.getChildSize(); i++) {
            createActionConfigs(parent.getChild(i), actionConfigs);
        }
        
        return actionConfigs;
    }

って感じで再帰的にしたら、うまくいくかも。

テスト実行。グリーンバー。


うまくいったけど、このメソッドも変な感じになってしまった。あーぁ


S2StrutsのPlugInのソースを参考にしてPlugInを作ろう。
以外に簡単に作れた。

後は、AuthActionにアノテーションを追加して、struts-config.xmlからAction-mappingを削除して、PlugInを追加してっと。


最終的にできたAuthActionは

public interface AuthAction {

    public static final String[] ACTION = { "path=/auth", "name=logonForm",
            "scope=request", "unknown=false", "validate=false" };

    public static final String[] logon_FORWARD = { "path=/do/site/top",
            "redirect=true" };

    public static final String[] logonError_FORWARD = { "path=/pages/logon.jsp" };

    public static final String[] logoff_FORWARD = { "path=/do/logon",
            "redirect=true" };

    public static final String logon_VALIDATE = "logonError";

    public String logon();

    public String logoff();

}

こんな感じ。


Tomcatを起動して手動による動作確認。
うまくいってる。


だけどあんまりうれしくないなー。

AnnotationActionConfigMapperクラスが変だからだな絶対。
きれいにしよ。



次はvalidator.xmlを??アノテーション化すること。