DynaActionFormで基底ActionFormを作っちゃおう!
の記事を受けまして、改良版です。
基本的には上記の記事と変わらず、DynaActionFormとActionServletを継承したクラスによりstruts-config.xmlの"includes"プロパティによって初期化処理時に継承していきます。
上記の記事で複数回継承できないのは、"includes"の有無を1度しか判定しないことと、"includes"自体をDynaActionFormのプロパティとして読み込んでしまうので、同名のプロパティが2つ定義されているのと同じ状態になることが原因でした。
そこで
DynaActionFormの継承クラスのinitialize()を
public void initialize(ActionMapping mapping) {
super.initialize(mapping);
String name = mapping.getName();
// フォームBEAN設定を取得
FormBeanConfig config = mapping.getModuleConfig().findFormBeanConfig(name);
// "includes"プロパティがあった場合、initialに指定されたフォームのプロパティを継承
FormPropertyConfig propConfig = config.findFormPropertyConfig("includes");
if (propConfig != null) {
// get the initial values
setFormPropertyMap(mapping, propConfig);
}
}
こんな感じに修正し、setFormPropertyMap()メソッドを追加
private void setFormPropertyMap(ActionMapping mapping, FormPropertyConfig propConfig) {
String formBeanToIncludes = propConfig.getInitial();
if (formBeanToIncludes != null) {
String[] includeForms = formBeanToIncludes.split(",");
for (String includeForm : includeForms) {
FormBeanConfig formBeanConfig = mapping.getModuleConfig().findFormBeanConfig(includeForm);
if (formBeanConfig != null) {
FormPropertyConfig includes = formBeanConfig.findFormPropertyConfig("includes");
//"includes"プロパティがあった場合は再帰呼び出し
if(includes != null){
setFormPropertyMap(mapping, includes);
}
//"includes"以外のプロパティのinitial値をセット
for (FormPropertyConfig prop : formBeanConfig.findFormPropertyConfigs()) {
if (!prop.getName().equals("includes")) {
if (prop.getInitial() != null) {
this.getMap().put(prop.getName(), prop.getInitial());
}
}
}
}
}
}
}
ActionServletの継承クラスも同様に
protected ModuleConfig initModuleConfig(String prefix, String paths) throws ServletException {
ModuleConfig config = super.initModuleConfig(prefix, paths);
//FromBeanConfigの取得
FormBeanConfig[] fbs = config.findFormBeanConfigs();
for (FormBeanConfig fb : fbs) {
//DynaActionFromだった場合、"includes"プロパティに従い継承処理
if (fb.getDynamic()) {
//"includes"プロパティの取得
FormPropertyConfig includes = fb.findFormPropertyConfig("includes");
if (includes != null) {
setFormPropertyIncludes(includes, config, fb);
}
DynaActionFormClass.createDynaActionFormClass(fb);
}
}
return config;
}
こんな感じに修正し、setFormPropertyIncludes()メソッドを追加
private void setFormPropertyIncludes(FormPropertyConfig includes, ModuleConfig config, FormBeanConfig fbs) {
String formBeanToIncludes = includes.getInitial();
if (formBeanToIncludes != null) {
String[] includeForms = formBeanToIncludes.split(",");
for (String formBeanToInclude : includeForms) {
//"includes"プロパティのinitial値から継承元Fromを取得
FormBeanConfig includeConfig = config.findFormBeanConfig(formBeanToInclude);
//継承元フォームからプロパティ群を取得
FormPropertyConfig[] props = includeConfig.findFormPropertyConfigs();
for (FormPropertyConfig prop : props) {
//"includes"プロパティがあった場合、再帰呼び出し
if (prop.getName().equals("includes")) {
setFormPropertyIncludes(prop, config, fbs);
} else {
//既に同名のプロパティが登録されている場合は登録しない
if (fbs.findFormPropertyConfig(prop.getName()) == null) {
fbs.addFormPropertyConfig(prop);
}
}
}
}
}
}
ちなみにですが、setFormPropertyIncludes()メソッドの上の方に
String[] includeForms = formBeanToIncludes.split(",");
こんな記載があります。
"includes"のinitial値をカンマで区切ることで複数回継承するだけでなく多重継承も出来るようにしているのですねぇ。
DynaActionFormをクラスと捉えると、多重継承はしないほうがいい気もするのですが、
あまり継承が深くなるとstruts-config内で継承関係を追いかけるのが困難になりそうなのでこの形式にしました。
という訳で、struts-config.xmlはこんな感じ。
<form-beans>
<!-- 基底フォーム その1 -->
<form-bean name="BaseForm1" type="org.apache.struts.action.DynaActionForm">
<form-property name="base1" type="java.lang.String" />
</form-bean>
<!-- 基底フォーム その2 -->
<form-bean name="BaseForm2" type="org.apache.struts.action.DynaActionForm">
<form-property name="base2" type="java.lang.String" />
</form-bean>
<!-- 基底フォーム その3 (その1を継承)-->
<form-bean name="BaseForm3" type="jp.co.ncad.common.BaseForm">
<!-- 基底フォームその1をインクルード -->
<form-property name="includes" type="java.lang.String" initial="BaseForm" />
<form-property name="base3" type="java.lang.String" />
<form-property name="base4" type="java.lang.String" />
</form-bean>
<!-- 基底フォームを継承するフォーム -->
<form-bean name="DataForm" type="jp.co.ncad.common.BaseForm">
<!-- 基底フォームをインクルード -->
<form-property name="includes" type="java.lang.String" initial="BaseForm2,BaseForm3" />
<form-property name="data1" type="java.lang.String" />
<form-property name="data2" type="java.lang.String" />
</form-bean>
</form-beans>
これでDataFormは基底フォーム1,2,3のプロパティとDataForm自身のプロパティ
つまりbase1,base2,base3,base4,data1,data2のプロパティを持ったフォームとなります。
継承元Formと継承先Formに同名のプロパティがあっても、片方は無視されて重複しないようになっています。
両方にinitialが指定されている場合継承先Form(クラスで言えば子クラス側)のinitial値が有効になります。
さすがに同一のForm内に同名のプロパティがあるのは許容しませんが。
しかしこの機能、struts標準で対応してくれませんかねぇ。
<form-extends>タグとか用意したらいいのに・・・
と思ったんですが、
struts-configのdtdを書き換えて<form-extends>タグを追加出来るようにして
ModuleConfigとかFormBeanConfigとかもオーバーライドすれば実装できるんじゃなかろうか。
そのうち試しますので、うまく出来たらまた報告します。