View Javadoc

1   /*
2    * Copyright 2004-2010 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  
17  package org.seasar.cubby.controller.impl;
18  
19  import static org.seasar.cubby.internal.util.LogMessages.format;
20  
21  import java.io.IOException;
22  import java.io.UnsupportedEncodingException;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.LinkedHashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  
30  import javax.servlet.http.HttpServletRequest;
31  
32  import org.apache.commons.fileupload.FileItem;
33  import org.apache.commons.fileupload.FileUpload;
34  import org.apache.commons.fileupload.FileUploadBase;
35  import org.apache.commons.fileupload.FileUploadException;
36  import org.apache.commons.fileupload.RequestContext;
37  import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
38  import org.seasar.cubby.controller.RequestParseException;
39  import org.seasar.cubby.controller.RequestParser;
40  import org.seasar.cubby.internal.util.StringUtils;
41  import org.seasar.cubby.spi.ContainerProvider;
42  import org.seasar.cubby.spi.ProviderFactory;
43  import org.seasar.cubby.spi.container.Container;
44  import org.seasar.cubby.spi.container.LookupException;
45  
46  /**
47   * マルチパートの要求に対応した解析器です。
48   * <p>
49   * 要求の解析には <a href="http://commons.apache.org/fileupload/">Commons
50   * FileUpload</a> を使用します。
51   * </p>
52   * 
53   * @author baba
54   * @see <a href="http://commons.apache.org/fileupload/">Commons FileUpload</a>
55   */
56  public class MultipartRequestParser implements RequestParser {
57  
58  	/**
59  	 * {@inheritDoc}
60  	 * <p>
61  	 * 指定された要求がマルチパートの要求 (contentType が "multipart/" で始まる) であれば、コンテナに登録された
62  	 * {@link FileUpload} と {@link RequestContext} を使用して要求を解析します。
63  	 * <p>
64  	 * 要求パラメータを戻り値の {@link Map} に格納する際には以下のように変換します。
65  	 * <ul>
66  	 * <li>フォームのフィールド
67  	 * <p>
68  	 * 文字列に変換
69  	 * </p>
70  	 * </li>
71  	 * <li>フォームのフィールド以外(アップロードされたファイル)
72  	 * <p>
73  	 * {@link FileItem} に変換
74  	 * </p>
75  	 * </li>
76  	 * </ul>
77  	 * </p>
78  	 * </p>
79  	 * 
80  	 * @throws RequestParseException
81  	 *             指定された要求がマルチパートではなかった場合
82  	 * @see FileUpload#parseRequest(RequestContext)
83  	 */
84  	@SuppressWarnings("unchecked")
85  	public Map<String, Object[]> getParameterMap(
86  			final HttpServletRequest request) {
87  		final Map<String, Object[]> parameterMap = new HashMap<String, Object[]>(
88  				request.getParameterMap());
89  		final Container container = ProviderFactory
90  				.get(ContainerProvider.class).getContainer();
91  		try {
92  			final RequestContext requestContext = container
93  					.lookup(RequestContext.class);
94  			final FileUpload fileUpload = container.lookup(FileUpload.class);
95  			final Map<String, Object[]> multipartParameterMap = getMultipartParameterMap(
96  					fileUpload, requestContext);
97  			parameterMap.putAll(multipartParameterMap);
98  			return parameterMap;
99  		} catch (final LookupException e) {
100 			throw new IllegalStateException(e);
101 		}
102 	}
103 
104 	@SuppressWarnings("unchecked")
105 	Map<String, Object[]> getMultipartParameterMap(final FileUpload fileUpload,
106 			final RequestContext requestContext) {
107 		try {
108 			final String encoding = requestContext.getCharacterEncoding();
109 			fileUpload.setHeaderEncoding(encoding);
110 			final List<FileItem> items = fileUpload
111 					.parseRequest(requestContext);
112 
113 			// Fieldごとにパラメータを集める
114 			final Map<String, Object[]> parameterMap = toParameterMap(encoding,
115 					items);
116 
117 			return parameterMap;
118 		} catch (final FileUploadException e) {
119 			final String messageCode;
120 			final Object[] args;
121 			if (e instanceof SizeLimitExceededException) {
122 				final SizeLimitExceededException sle = (SizeLimitExceededException) e;
123 				messageCode = "ECUB0202";
124 				args = new Object[] { sle.getPermittedSize(),
125 						sle.getActualSize() };
126 			} else {
127 				messageCode = "ECUB0201";
128 				args = new Object[] { e };
129 			}
130 			throw new RequestParseException(format(messageCode, args), e);
131 		} catch (final IOException e) {
132 			throw new RequestParseException(e);
133 		}
134 	}
135 
136 	Map<String, Object[]> toParameterMap(final String encoding,
137 			final List<FileItem> items) throws UnsupportedEncodingException {
138 		final Map<String, List<Object>> valueListParameterMap = new LinkedHashMap<String, List<Object>>();
139 		for (final FileItem item : items) {
140 			final Object value;
141 			if (item.isFormField()) {
142 				value = item.getString(encoding);
143 			} else {
144 				if (StringUtils.isEmpty(item.getName()) || item.getSize() == 0) {
145 					// ファイル名無し、あるいは0バイトのファイル
146 					value = null;
147 				} else {
148 					value = item;
149 				}
150 			}
151 			final List<Object> values;
152 			if (valueListParameterMap.containsKey(item.getFieldName())) {
153 				values = valueListParameterMap.get(item.getFieldName());
154 			} else {
155 				values = new ArrayList<Object>();
156 				valueListParameterMap.put(item.getFieldName(), values);
157 			}
158 			values.add(value);
159 		}
160 
161 		final Map<String, Object[]> parameterMap = fromValueListMapToValueArrayMap(valueListParameterMap);
162 		return parameterMap;
163 	}
164 
165 	Map<String, Object[]> fromValueListMapToValueArrayMap(
166 			final Map<String, List<Object>> valueListMap) {
167 		// 配列でパラメータMapを構築
168 		final Map<String, Object[]> parameterMap = new HashMap<String, Object[]>();
169 		for (final Entry<String, List<Object>> entry : valueListMap.entrySet()) {
170 			final List<Object> values = entry.getValue();
171 			final Object[] valueArray;
172 			if (values.get(0) instanceof String) {
173 				valueArray = new String[values.size()];
174 			} else {
175 				valueArray = new FileItem[values.size()];
176 			}
177 			parameterMap.put(entry.getKey(), values.toArray(valueArray));
178 		}
179 		return parameterMap;
180 	}
181 
182 	/**
183 	 * {@inheritDoc}
184 	 * <p>
185 	 * 指定された要求がマルチパートの要求 (contentType が "multipart/" で始まる) の場合に
186 	 * <code>true</code> を返します。
187 	 * </p>
188 	 * 
189 	 * @see FileUpload#isMultipartContent(RequestContext)
190 	 */
191 	public boolean isParsable(final HttpServletRequest request) {
192 		final Container container = ProviderFactory
193 				.get(ContainerProvider.class).getContainer();
194 		try {
195 			final RequestContext requestContext = container
196 					.lookup(RequestContext.class);
197 			return FileUploadBase.isMultipartContent(requestContext);
198 		} catch (final LookupException e) {
199 			return false;
200 		}
201 	}
202 
203 }