View Javadoc

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