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 					// ファイル名無し、あるいは０バイトのファイル
    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 }