View Javadoc

1   /*
2    * Copyright 2004-2008 the Seasar Foundation and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.seasar.cubby.controller.impl;
17  
18  import java.io.IOException;
19  import java.io.UnsupportedEncodingException;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.LinkedHashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  
27  import javax.servlet.http.HttpServletRequest;
28  
29  import org.apache.commons.fileupload.FileItem;
30  import org.apache.commons.fileupload.FileUpload;
31  import org.apache.commons.fileupload.FileUploadException;
32  import org.apache.commons.fileupload.RequestContext;
33  import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
34  import org.apache.commons.fileupload.servlet.ServletFileUpload;
35  import org.seasar.cubby.controller.RequestParser;
36  import org.seasar.cubby.exception.FileUploadRuntimeException;
37  import org.seasar.framework.container.S2Container;
38  import org.seasar.framework.exception.IORuntimeException;
39  import org.seasar.framework.util.StringUtil;
40  
41  /**
42   * contentType が multipart/form-data のリクエストに対応したリクエスト解析器です。
43   * <p>
44   * リクエストの解析には <a href="http://commons.apache.org/fileupload/">Commons FileUpload</a>
45   * を使用します。
46   * </p>
47   * 
48   * @author baba
49   * @see <a href="http://commons.apache.org/fileupload/">Commons FileUpload</a>
50   * @since 1.0.0
51   */
52  public class MultipartRequestParserImpl implements RequestParser {
53  
54  	/** デフォルトの優先順位。 */
55  	static final int DEFAULT_PRIORITY = DefaultRequestParserImpl.DEFAULT_PRIORITY - 1;
56  
57  	/** コンテナ。 */
58  	private final S2Container container;
59  
60  	/** 優先順位。 */
61  	private int priority = DEFAULT_PRIORITY;
62  
63  	/**
64  	 * インスタンス化します。
65  	 * 
66  	 * @param container
67  	 *            コンテナ
68  	 */
69  	public MultipartRequestParserImpl(final S2Container container) {
70  		this.container = container;
71  	}
72  
73  	/**
74  	 * {@inheritDoc}
75  	 * <p>
76  	 * 指定されたリクエストがマルチパートのリクエスト(contentType が multipart/form-data)であれば、コンテナに登録された
77  	 * {@link FileUpload} と {@link RequestContext} を使用してリクエストを解析します。
78  	 * <p>
79  	 * リクエストパラメータを戻り値の {@link Map} に格納する際には以下のように変換します。
80  	 * <ul>
81  	 * <li> フォームのフィールド
82  	 * <p>
83  	 * 文字列に変換
84  	 * </p>
85  	 * </li>
86  	 * <li> フォームのフィールド以外(アップロードされたファイル)
87  	 * <p>
88  	 * {@link FileItem}に変換
89  	 * </p>
90  	 * </li>
91  	 * </ul>
92  	 * </p>
93  	 * </p>
94  	 * <p>
95  	 * 指定されたリクエストが通常のリクエストであれば、{@link HttpServletRequest#getParameterMap()}
96  	 * の結果をそのまま返します。
97  	 * </p>
98  	 * 
99  	 * @see FileUpload
100 	 */
101 	@SuppressWarnings("unchecked")
102 	public Map<String, Object[]> getParameterMap(
103 			final HttpServletRequest request) {
104 		final Map<String, Object[]> parameterMap = new HashMap<String, Object[]>(
105 				request.getParameterMap());
106 		if (ServletFileUpload.isMultipartContent(request)) {
107 			final S2Container root = container.getRoot();
108 			final FileUpload fileUpload = (FileUpload) root
109 					.getComponent(FileUpload.class);
110 			final RequestContext requestContext = (RequestContext) root
111 					.getComponent(RequestContext.class);
112 			parameterMap.putAll(this.getMultipartParameterMap(fileUpload,
113 					requestContext));
114 		}
115 		return parameterMap;
116 	}
117 
118 	@SuppressWarnings("unchecked")
119 	Map<String, Object[]> getMultipartParameterMap(final FileUpload fileUpload,
120 			final RequestContext requestContext) {
121 		try {
122 			final String encoding = requestContext.getCharacterEncoding();
123 			fileUpload.setHeaderEncoding(encoding);
124 			final List<FileItem> items = fileUpload
125 					.parseRequest(requestContext);
126 
127 			// Fieldごとにパラメータを集める
128 			final Map<String, Object[]> parameterMap = toParameterMap(encoding,
129 					items);
130 
131 			return parameterMap;
132 		} catch (final FileUploadException e) {
133 			final String messageCode;
134 			final Object[] args;
135 			if (e instanceof SizeLimitExceededException) {
136 				final SizeLimitExceededException sle = (SizeLimitExceededException) e;
137 				messageCode = "ECUB0202";
138 				args = new Object[] { sle.getPermittedSize(),
139 						sle.getActualSize() };
140 			} else {
141 				messageCode = "ECUB0201";
142 				args = new Object[] { e };
143 			}
144 			throw new FileUploadRuntimeException(messageCode, args, e);
145 		} catch (final IOException e) {
146 			throw new IORuntimeException(e);
147 		}
148 	}
149 
150 	Map<String, Object[]> toParameterMap(final String encoding,
151 			final List<FileItem> items) throws UnsupportedEncodingException {
152 		final Map<String, List<Object>> valueListParameterMap = new LinkedHashMap<String, List<Object>>();
153 		for (final FileItem item : items) {
154 			final Object value;
155 			if (item.isFormField()) {
156 				value = item.getString(encoding);
157 			} else {
158 				if (StringUtil.isEmpty(item.getName()) || item.getSize() == 0) {
159 					// ファイル名無し、あるいは0バイトのファイル
160 					value = null;
161 				} else {
162 					value = item;
163 				}
164 			}
165 			final List<Object> values;
166 			if (valueListParameterMap.containsKey(item.getFieldName())) {
167 				values = valueListParameterMap.get(item.getFieldName());
168 			} else {
169 				values = new ArrayList<Object>();
170 				valueListParameterMap.put(item.getFieldName(), values);
171 			}
172 			values.add(value);
173 		}
174 
175 		final Map<String, Object[]> parameterMap = fromValueListToValueArray(valueListParameterMap);
176 		return parameterMap;
177 	}
178 
179 	Map<String, Object[]> fromValueListToValueArray(
180 			final Map<String, List<Object>> collectParameterMap) {
181 		// 配列でパラメータMapを構築
182 		final Map<String, Object[]> parameterMap = new HashMap<String, Object[]>();
183 		for (final Entry<String, List<Object>> entry : collectParameterMap
184 				.entrySet()) {
185 			final List<Object> values = entry.getValue();
186 			final Object[] valueArray;
187 			if (values.get(0) instanceof String) {
188 				valueArray = new String[values.size()];
189 			} else {
190 				valueArray = new FileItem[values.size()];
191 			}
192 			parameterMap.put(entry.getKey(), values.toArray(valueArray));
193 		}
194 		return parameterMap;
195 	}
196 
197 	/**
198 	 * {@inheritDoc}
199 	 */
200 	public boolean isParsable(final HttpServletRequest request) {
201 		final S2Container root = container.getRoot();
202 		if (root.hasComponentDef(RequestContext.class)) {
203 			final RequestContext requestContext = (RequestContext) root
204 					.getComponent(RequestContext.class);
205 			return FileUpload.isMultipartContent(requestContext);
206 		}
207 		return false;
208 	}
209 
210 	/**
211 	 * {@inheritDoc}
212 	 * <p>
213 	 * デフォルトの優先順位は {@link DefaultRequestParserImpl#DEFAULT_PRIORITY} - 1 です。
214 	 * </p>
215 	 */
216 	public int getPriority() {
217 		return priority;
218 	}
219 
220 	/**
221 	 * 優先順位を設定します。
222 	 * 
223 	 * @param priority
224 	 *            優先順位
225 	 */
226 	public void setPriority(final int priority) {
227 		this.priority = priority;
228 	}
229 
230 }