はじめに
HTML5で<input type="file" />
にmultiple
属性が追加され、ブラウザのファイル選択画面で複数のファイルが一度に選択でき、サーバへの送信も一度にできるようになった。
SAStrutsはHTML5時代に作られたものではないので、ActionFormでのファイル受取はファイルが一つしか来ないものとして実装されている。フレームワーク部分を拡張してファイルを複数受け取れるようにする。
jspとActionForm
sample.jsp
<input type="file" name="fileDatas" multiple="multiple" />
SampleForm.java
public class SampleForm implements Serializable {
private FormFile[] fileDatas; // getter, setter 略
}
ファイルを受け取る箇所は、FormFileの配列にする。
S2MultipartRequestHandler
リクエスト内容をActionFormに詰めるための前処理としてリクエスト内容の分解と解釈を、SAStrutsで用意されているS2MultipartRequestHandler
クラスが行っている。addTextParameter
とaddFileParameter
というメソッドで、それぞれリクエストの文字列とファイルをActionFormに詰めるための前処理を行っている。文字列のほうは配列に対応できるように全て配列として扱っているのだが、ファイルの方は単体としてしか扱っていないため、addTextParameter
を参考にしてaddFileParameter
をOverrideする。
import org.apache.commons.fileupload.FileItem;
import org.apache.struts.upload.FormFile;
import org.seasar.struts.upload.S2MultipartRequestHandler;
public class MyS2MultipartRequestHandler extends S2MultipartRequestHandler {
@SuppressWarnings("unchecked")
@Override
protected void addFileParameter(FileItem item) {
String name = item.getFieldName();
FormFile value = new S2FormFile(item);
FormFile[] oldArray = (FormFile[]) elementsFile.get(name);
FormFile[] newArray;
if (oldArray != null) {
newArray = new FormFile[oldArray.length + 1];
System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
newArray[oldArray.length] = value;
} else {
newArray = new FormFile[] { value };
}
elementsFile.put(name, newArray);
elementsAll.put(name, newArray);
}
}
S2RequestProcessor
S2RequestProcessor
は、数あるActionのうち何を使うか等を解決し、processActionPerform
メソッドで実際にActionのメソッドを呼び出す。SAStrutsでの基幹部分となるクラスだ。
S2MultipartRequestHandler
もS2RequestProcessor
のprocessPopulate
メソッドに呼び出されている。このメソッドの終盤でsetProperty
メソッドが呼ばれ、ActionFormの各変数にS2MultipartRequestHandler
で前処理された値をセットしている。
S2MultipartRequestHandler#addFileParameter
をOverrideしたため、S2RequestProcessor#setProperty
内においても、FormFileを単体として扱っていたものが配列として扱わなければいけなくなった。setProperty
で実際に値をセットしている箇所は次の三つのメソッドに切り出されている。
- setSimpleProperty
- setMapProperty ※setSimplePropertyから呼ばれる
- setIndexedProperty
これらのメソッドでString配列がどのように処理されているのかを確認し、String[]の処理と同様にFormFile[]の処理を実装するようOverrideする。
継承したクラスとOverrideしたメソッドは以下の通り。既存の処理にelse if (value instanceof FormFile[])
を追加している。
※setIndexedPropertyについては、String[]の処理は行われていないため、Overrideしていない。
public class MyS2RequestProcessor extends S2RequestProcessor {
/**
* 単純なプロパティの値を設定します。
*
* @param bean
* JavaBeans
* @param name
* パラメータ名
* @param value
* パラメータの値
* @throws ServletException
* 何か例外が発生した場合。
*/
@SuppressWarnings("unchecked")
@Override
protected void setSimpleProperty(Object bean, String name, Object value) {
if (bean instanceof Map) {
setMapProperty((Map) bean, name, value);
return;
}
BeanDesc beanDesc = BeanDescFactory.getBeanDesc(bean.getClass());
if (!beanDesc.hasPropertyDesc(name)) {
return;
}
PropertyDesc pd = beanDesc.getPropertyDesc(name);
if (!pd.isWritable()) {
return;
}
if (pd.getPropertyType().isArray()) {
pd.setValue(bean, value);
} else if (List.class.isAssignableFrom(pd.getPropertyType())) {
List<String> list = ModifierUtil.isAbstract(pd.getPropertyType()) ? new ArrayList<String>()
: (List<String>) ClassUtil
.newInstance(pd.getPropertyType());
list.addAll(Arrays.asList((String[]) value));
pd.setValue(bean, list);
} else if (value == null) {
pd.setValue(bean, null);
} else if (value instanceof String[]) {
String[] values = (String[]) value;
pd.setValue(bean, values.length > 0 ? values[0] : null);
} else if (value instanceof FormFile[]) {
FormFile[] values = (FormFile[]) value;
pd.setValue(bean, values.length > 0 ? values[0] : null);
} else {
pd.setValue(bean, value);
}
}
/**
* Mapの値を設定します。
*
* @param map
* マップ
* @param name
* キー名
* @param value
* 値
*/
@SuppressWarnings("unchecked")
@Override
protected void setMapProperty(Map map, String name, Object value) {
if (value instanceof String[]) {
String[] values = (String[]) value;
map.put(name, values.length > 0 ? values[0] : null);
} else if (value instanceof FormFile[]) {
FormFile[] values = (FormFile[]) value;
map.put(name, values.length > 0 ? values[0] : null);
} else {
map.put(name, value);
}
}
}
※ActionForm内でFormFileを配列で定義しているときは、S2MultipartRequestHandler
の拡張をするだけでも動くのだが、FormFileを単体で定義すると動かなくなってしまう。上に書いた拡張を行えば単体でも動く。
struts-config.xml
S2MultipartRequestHandler
とS2RequestProcessor
はstruts-config.xml
のcontroller
タグで設定することになっているが、独自クラスを継承して作ったため、明示的に設定が必要になる。
略
<controller
maxFileSize="250M"
bufferSize="1024"
processorClass="jp.co.sample.framework.processor.MyS2RequestProcessor"
multipartClass="jp.co.sample.framework.handler.MyS2MultipartRequestHandler"
/>
略
processorClass
とmultipartClass
にそれぞれ継承実装したクラスを設定しているのは見ての通りとして、maxFileSize
については注意が必要となる。
maxFileSize
はアップロードされるファイルのサイズを制限する数値だが、ファイルを複数アップロードした時はファイル一つ一つに制限値が適用されるのではなく、複数まとめたサイズに対して適用される。
ファイルを一つだけアップロードした時には十分だった値も、ファイルを複数アップロードするとなると途端に制限が厳しすぎるということになりかねないので、適切な値に設定し直す必要がある。