Struts 1.2 を利用するにあたり「こんな使い方あるよ」な「逆引き辞典」的な記録方法になります。
「性別:男性」といったSELECTタグを実装する方法です。
一般的にStrutsではFORMに具体的な値(男性を表す"1"など)が設定されています。これをJSPに表示する際に「男性」と表示するには以下を参考にして下さい。
public class HogeServlet extends HttpServlet {
public class Personal {
private String id;
private String gender;
// Getter,Setterは省略
}
public void doGet(HttpServletRequest request, HttpServletResponse response) {
Personal dto = new Personal();
dto.setGender("1"); // genderの先頭が大文字なのは注意
request.setAttribute("DTO", dto);
Map map = new TreeMap();
map.put("1", "男性");
map.put("2", "女性");
request.setAttribute("genderMap", map);
request.getRequestDispatcher("hoge.jsp").forward(request, response);
}
}
<html:select name="DTO" property="gender">
<html:option value="">選択して下さい</html:option> <!-- この行はなくてもよい -->
<html:optionsCollection name="genderMap" value="key" label="value" />
</html:select>
上記のSELECTタグをradioにて出力する実装する方法です。
<logic:iterate id="row" name="genderMap">
<html:radio idName="row" name="DTO" property="gender" value="key" />
<bean:write name="row" property="value"/>
</logic:iterate>
上記のSELECTタグをプレーンテキストにて出力する実装する方法です。
<c:out value="${genderMap[DTO.gender]}" />
<bean:define id="genderValue" name="DTO" property="gender" type="java.lang.String" />
<bean:write name="genderMap" property="<%=genderValue%>" />
Strutsを利用する方で知名度が低いのがactionタグのparameter属性かと思います。まずは使い方です。
<struts-config>
<action-mappings type="org.apache.struts.action.ActionMapping">
<action path="/likePerson"
type="com.hogehoge.LikeAction" name="Person"
scope="session" validate="false"
parameter="LikePerson">
<forward name="success" path="/WEB-INF/jsp/likedPerson.jsp"
redirect="false">
<forward name="notfound" path="/WEB-INF/jsp/likedPerson.jsp"
redirect="false">
</action>
</action-mappings>
</struts-config>
path="/likePerson"にて呼び出される設定が記述してあります。ここでparameter属性には"LikePerson"が設定されています。これをJavaで利用します。
package com.hogehoge;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
public class LikeAction extends Action {
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
System.out.println(mapping.getParameter());
return mapping.findForward("success");
}
}
Javaで利用するにはActionMapping#getParameter()を呼び出すのみです。戻り値はStringですのでStrutsConfigに記述してあるparameter属性の値(今回でいうとparameter="LikePerson"なので"LikePerson")が取得できます。
単に特定の文字列を渡すだけの機能ですが(ForwardActionはその典型でしょう)、このパラメータにロジッククラスやサービスクラスといったクラスのFQCNを記述することで、Actionからインスタンス生成などに利用することができます。
これ単体では目立った機能ではないのですがワイルドカードマッピングと組み合わせるととても強力です。
Validator機能を利用する際、こちらのようなソースをみたことがあると思います。このset-propertyタグは非常に考えられた優秀なタグなので紹介します。
set-propertyタグは単純に値をセッタするためのタグになります。つまりValidatorプラグインではValidatorPlugInをインスタンス化したのち、pathnamesという名のプロパティに"/validator-rules.xml,/validation.xml"という値を設定しているというだけなのです。
このset-propertyタグが「考慮された優秀な」というのは、非常に多くの親要素の子要素として記述することができるからです。
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames"
value="/validator-rules.xml,/validation.xml">
</plug-in>
こちらのStrutsConfigを参照下さい。こちらにはactionのpathに/blankが指定された例になります。こちらではactionタグに利用するクラスがActionMappingと指定されています。このクラスを拡張してみます。
<struts-config>
<action-mappings type="org.apache.struts.action.ActionMapping">
<action path="/blank"
type="org.apache.struts.actions.ForwardAction"
parameter="/WEB-INF/jsp/blank.jsp">
</action>
</action-mappings>
</struts-config>
こちらが拡張したクラスになります。拡張といってもプロパティを増やした程度のクラスです。
public class ActionMappingEx extends ActionMapping {
private String parameter1;
// getter,setter等、説明に不要な記述は省略
}
こちらのparameter1に値を設定してみます。
<struts-config>
<action-mappings type="org.apache.struts.action.ActionMappingEx">
<action path="/blank"
type="org.apache.struts.actions.ForwardAction"
parameter="/WEB-INF/jsp/blank.jsp">
<set-property property="parameter1" value="hogehoge">
</action>
</action-mappings>
</struts-config>
このようにdtdファイルを拡張しなくても、プロパティを増やすことができる仕組みになっています。優秀な作りだと思います。
またこのタグはdtdファイルによると、data-source,form-bean,form-property,exception,forward,action,controller,message-resources,plug-inの子要素として記述することができます。
ServletではURLのマッピングに先頭もしくは末尾に*(アスタリスク)を用いることができます。ですが若干不便です。このマッピングをカバーする機能としてワイルドカードマッピングという機能があります(上位互換と思って下さい)。
現在、下記StrutsConfigにはPersonという名の名詞(データの塊の意)とblank, condition, likeという動詞(名詞に対する動作)から構成されているようです。
<struts-config>
<form-beans type="org.apache.struts.action.ActionFormBean">
<form-bean name="Person" type="com.hogehoge.PersonForm"/>
</form-beans>
<action-mappings type="org.apache.struts.action.ActionMapping">
<action path="/blankPerson"
type="org.apache.struts.actions.ForwardAction"
parameter="/WEB-INF/jsp/blankPerson.jsp">
<action path="/conditionPerson"
type="org.apache.struts.actions.ForwardAction"
parameter="/WEB-INF/jsp/conditionPerson.jsp"
name="Person" validate="false">
<action path="/likePerson"
type="com.hogehoge.LikeAction" name="Person"
scope="session" validate="false"
parameter="LikePerson">
<forward name="success" path="/WEB-INF/jsp/likedPerson.jsp"
redirect="false">
<forward name="notfound" path="/WEB-INF/jsp/likedPerson.jsp"
redirect="false">
</action>
</action-mappings>
</struts-config>
今回は上記のStrutsConfigをワイルドカードマッピングに対応してみます。
まずはactionタグのpath属性の中で、名詞であるPersonを*(アスタリスク)に置き換えます。actionタグの他の属性のPersonは{1}に置き換えます。これにて終了です。
<struts-config>
<form-beans type="org.apache.struts.action.ActionFormBean">
<form-bean name="Person" type="com.hogehoge.PersonForm"/>
</form-beans>
<action-mappings type="org.apache.struts.action.ActionMapping">
<action path="/blank*"
type="org.apache.struts.actions.ForwardAction"
parameter="/WEB-INF/jsp/blank{1}.jsp">
<action path="/condition*"
type="org.apache.struts.actions.ForwardAction"
parameter="/WEB-INF/jsp/condition{1}.jsp"
name="{1}" validate="false">
<action path="/like*"
type="com.hogehoge.LikeAction" name="{1}"
scope="session" validate="false"
parameter="Like{1}">
<forward name="success" path="/WEB-INF/jsp/liked{1}.jsp"
redirect="false">
<forward name="notfound" path="/WEB-INF/jsp/liked{1}.jsp"
redirect="false">
</action>
</action-mappings>
</struts-config>
この作業を行うことによって、いままでは「path="/blankPerson"」のみに対応していましたが「path="/blankBook"」「path="/blankCompany"」にも対応しました。
ただし当然ですが「name="Book"」であるActionフォームが存在しません。前記の対応を行わないと動作させられませんが、StrutsConfigファイルの記述量は相当量削減したと思います。
同じ名前でFormFileを複数Submitするサンプルになります。
<html:form action="それぞれ" enctype="multipart/form-data">
<html:file property="file[0]" /><br />
<html:file property="file[1]" /><br />
<html:file property="file[2]" /><br />
<html:submit />
</html:form>
import java.util.HashMap;
import java.util.Map;
import org.apache.struts.upload.FormFile;
import org.apache.struts.validator.ActionForm;
@SuppressWarnings("serial")
public class MultipleFormFileForm extends ActionForm {
private Map<Integer, FormFile> map = new HashMap<>();
public FormFile getFile(int index) {
return map.get(index);
}
public void setFile(int index, FormFile file) {
map.put(index, file);
}
public FormFile[] getFiles() {
return map.values().toArray(new FormFile[map.size()]);
}
}
BeanUtilsの脆弱性として「class.classLoader.xxx」のように指定するとgetClass().getClassLoader().setXxxx(???)のように動作してしまうものがあります。
こちらの脆弱性を対応するために以下を利用します。
package jp.co.mclnet.servlet30;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
@WebListener
public class SuppressPropertiesListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
PropertyUtils.addBeanIntrospector(
SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
PropertyUtils.clearDescriptors();
}
}
「SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS」定数を参照すると以下のようなことも対応できそうです。
String[] properties = { "test", "other", "oneMore" };
SuppressPropertiesBeanIntrospector introspector
= new SuppressPropertiesBeanIntrospector(Arrays.asList(properties));
また、こちらのクラスは「@WebListener」アノテーションを利用していますのでweb.xmlに登録は不要ですが、具体的に記述する場合は以下を参考にして下さい。
<listener>
<listener-class>jp.co.mclnet.servlet30.SuppressPropertiesListener</listener-class>
</listener>