Struts1.2.xでファイルアップロード

ちと最近、Struts1.2.xでファイルアップロードやって面倒だったことがあったので、いまさらまとめ。

マルチパートフォーム処理のアクションから別アクションにフォワードさせると、フォワード先ではパラメーターがアクションフォームに設定されない。

でも、getParameterでは取得できる。


ということでgetParameterした値をアクションフォームに設定するアクションをフォワードの間に挟むと解決できる。
org.apache.struts.util.RequestUtils#populate(java.lang.Object, java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
の処理そのまんまです。

public class ForwardFromMultipartAction extends Action {

  @Override
  public String doAction(ApRequestContext ctx, ApForm actionForm) {

    Enumeration<?> names = getHttpServletRequest(ctx).getParameterNames();
    HashMap<String, Object> properties = new HashMap<String, Object>();

    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      Object parameterValue = getHttpServletRequest(ctx)
          .getParameterValues(name);

          if (!(name.startsWith("org.apache.struts."))) {
              properties.put(name, parameterValue);
          }
    }
    try {
      BeanUtils.populate(actionForm, properties);
    } catch (Exception e) {
      throw new ApplicationException("BeanUtils.populate", e);
    }
    return FWD_SUCCESS;
  }
}

あとはstruts-config.xmlでA→BだったのをA→上記のアクション→BとすればOK。

ファイルアップロードの入れ子(ネスト)

ファイルアップロードの話というより、struts入れ子Listのパラメータ設定の話です。

FormFileを要素に持つListをFileListとして、またそのFileListを要素に持つListをFileListListとしてみる。

ということで、アクションフォームはこんな風に作る。

/** 全レコードのアップロードファイルリスト */
private FileListList fileListList = new FileListList();

/**
 * 全レコードのアップロードファイルリスト を返します.
 * @return 全レコードのアップロードファイルリスト
 */
public FileListList getFileListList() {
  return fileListList;
}

/**
 * 全レコードのアップロードファイルリストを設定します.
 * @param fileListList 全回答のアップロードファイルリスト
 */
public void setFileListList(FileListList fileListList) {
  this.fileListList = fileListList;
}

/**
 * 全レコードのアップロードファイルを保持するリスト
 */
public class FileListList extends ArrayList<FileList> {
  
  public FileList get(int index) {
    while (this.size() <= index) {
      this.add(new FileList());
    }
    return super.get(index);
  }
}

/**
 * 各レコードのアップロードファイルを保持するリスト
 */
public class FileList extends ArrayList<FormFile> {
  
  public void setFormFile(int index, FormFile formFile) {
    this.add(index, formFile);
  }
}

HTMLがこんな感じで出力されるように、JSPを書く。
1レコード目
<input type="file" value="" name="fileListList[0].formFile[0]"/>
<input type="file" value="" name="fileListList[0].formFile[1]"/>

2レコード目
<input type="file" value="" name="fileListList[1].formFile[0]"/>
<input type="file" value="" name="fileListList[1].formFile[1]"/>

3レコード目
<input type="file" value="" name="fileListList[2].formFile[0]"/>


例として「fileListList[2].formFile[0]」の値がアクションフォームに設定されるロジックを見て理解してみる。

  1. org.apache.commons.beanutils.PropertyUtilsBean#getIndexedProperty()がfileListList.get(2)を呼び出す。
  2. 上記で呼び出して取得したインスタンスに対しorg.apache.commons.beanutils.PropertyUtilsBean#setIndexedProperty()が取得したインスタンス.setFormFile(0, FormFileインスタンス)を呼び出す。

つまり

  • fileListList.get(2).setFormFile(0, FormFileインスタンス)、つまりFileListListの2番目のFileListのsetFormFileを使って0番目にFormFileをセットする。
  • FileListListやFileListがArrayListを継承する理由は以下のメソッドでListの場合の処理を行ってほしいため。
    • PropertyUtilsBean#getIndexedProperty()
    • PropertyUtilsBean#setIndexedProperty()
  • FileListListでget()をオーバーライドしているのはget(int)が確実に値を返す必要があるから。
  • FileListでsetFormFile()を作成したのはPropertyUtilsBean#setIndexedProperty()がformFile[n]という文字列からsetFormFile(int, FormFile)を呼び出すからファイル要素のnameに対応させる必要がある。