001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.web.multipart.commons;
018:
019: import java.io.IOException;
020: import java.io.UnsupportedEncodingException;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.commons.fileupload.FileItem;
028: import org.apache.commons.fileupload.FileItemFactory;
029: import org.apache.commons.fileupload.FileUpload;
030: import org.apache.commons.fileupload.disk.DiskFileItemFactory;
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033:
034: import org.springframework.core.io.Resource;
035: import org.springframework.util.StringUtils;
036: import org.springframework.web.multipart.MultipartException;
037: import org.springframework.web.util.WebUtils;
038:
039: /**
040: * Base class for multipart resolvers that use Jakarta Commons FileUpload
041: * 1.1 or higher.
042: *
043: * <p>Provides common configuration properties and parsing functionality
044: * for multipart requests, using a Map of Spring CommonsMultipartFile instances
045: * as representation of uploaded files and a String-based parameter Map as
046: * representation of uploaded form fields.
047: *
048: * <p>Subclasses implement concrete resolution strategies for Servlet or Portlet
049: * environments: see CommonsMultipartResolver and CommonsPortletMultipartResolver,
050: * respectively. This base class is not tied to either of those APIs, factoring
051: * out common functionality.
052: *
053: * @author Juergen Hoeller
054: * @since 2.0
055: * @see CommonsMultipartFile
056: * @see CommonsMultipartResolver
057: * @see org.springframework.web.portlet.multipart.CommonsPortletMultipartResolver
058: */
059: public abstract class CommonsFileUploadSupport {
060:
061: protected final Log logger = LogFactory.getLog(getClass());
062:
063: private final DiskFileItemFactory fileItemFactory;
064:
065: private final FileUpload fileUpload;
066:
067: private boolean uploadTempDirSpecified = false;
068:
069: /**
070: * Instantiate a new CommonsFileUploadSupport with its
071: * corresponding FileItemFactory and FileUpload instances.
072: * @see #newFileItemFactory
073: * @see #newFileUpload
074: */
075: public CommonsFileUploadSupport() {
076: this .fileItemFactory = newFileItemFactory();
077: this .fileUpload = newFileUpload(getFileItemFactory());
078: }
079:
080: /**
081: * Return the underlying <code>org.apache.commons.fileupload.disk.DiskFileItemFactory</code>
082: * instance. There is hardly any need to access this.
083: * @return the underlying DiskFileItemFactory instance
084: */
085: public DiskFileItemFactory getFileItemFactory() {
086: return this .fileItemFactory;
087: }
088:
089: /**
090: * Return the underlying <code>org.apache.commons.fileupload.FileUpload</code>
091: * instance. There is hardly any need to access this.
092: * @return the underlying FileUpload instance
093: */
094: public FileUpload getFileUpload() {
095: return this .fileUpload;
096: }
097:
098: /**
099: * Set the maximum allowed size (in bytes) before uploads are refused.
100: * -1 indicates no limit (the default).
101: * @param maxUploadSize the maximum upload size allowed
102: * @see org.apache.commons.fileupload.FileUploadBase#setSizeMax
103: */
104: public void setMaxUploadSize(long maxUploadSize) {
105: this .fileUpload.setSizeMax(maxUploadSize);
106: }
107:
108: /**
109: * Set the maximum allowed size (in bytes) before uploads are written to disk.
110: * Uploaded files will still be received past this amount, but they will not be
111: * stored in memory. Default is 10240, according to Commons FileUpload.
112: * @param maxInMemorySize the maximum in memory size allowed
113: * @see org.apache.commons.fileupload.disk.DiskFileItemFactory#setSizeThreshold
114: */
115: public void setMaxInMemorySize(int maxInMemorySize) {
116: this .fileItemFactory.setSizeThreshold(maxInMemorySize);
117: }
118:
119: /**
120: * Set the default character encoding to use for parsing requests,
121: * to be applied to headers of individual parts and to form fields.
122: * Default is ISO-8859-1, according to the Servlet spec.
123: * <p>If the request specifies a character encoding itself, the request
124: * encoding will override this setting. This also allows for generically
125: * overriding the character encoding in a filter that invokes the
126: * <code>ServletRequest.setCharacterEncoding</code> method.
127: * @param defaultEncoding the character encoding to use
128: * @see javax.servlet.ServletRequest#getCharacterEncoding
129: * @see javax.servlet.ServletRequest#setCharacterEncoding
130: * @see WebUtils#DEFAULT_CHARACTER_ENCODING
131: * @see org.apache.commons.fileupload.FileUploadBase#setHeaderEncoding
132: */
133: public void setDefaultEncoding(String defaultEncoding) {
134: this .fileUpload.setHeaderEncoding(defaultEncoding);
135: }
136:
137: protected String getDefaultEncoding() {
138: String encoding = getFileUpload().getHeaderEncoding();
139: if (encoding == null) {
140: encoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
141: }
142: return encoding;
143: }
144:
145: /**
146: * Set the temporary directory where uploaded files get stored.
147: * Default is the servlet container's temporary directory for the web application.
148: * @see org.springframework.web.util.WebUtils#TEMP_DIR_CONTEXT_ATTRIBUTE
149: */
150: public void setUploadTempDir(Resource uploadTempDir)
151: throws IOException {
152: if (!uploadTempDir.exists()
153: && !uploadTempDir.getFile().mkdirs()) {
154: throw new IllegalArgumentException("Given uploadTempDir ["
155: + uploadTempDir + "] could not be created");
156: }
157: this .fileItemFactory.setRepository(uploadTempDir.getFile());
158: this .uploadTempDirSpecified = true;
159: }
160:
161: protected boolean isUploadTempDirSpecified() {
162: return this .uploadTempDirSpecified;
163: }
164:
165: /**
166: * Factory method for a Commons DiskFileItemFactory instance.
167: * <p>Default implementation returns a standard DiskFileItemFactory.
168: * Can be overridden to use a custom subclass, e.g. for testing purposes.
169: * @return the new DiskFileItemFactory instance
170: */
171: protected DiskFileItemFactory newFileItemFactory() {
172: return new DiskFileItemFactory();
173: }
174:
175: /**
176: * Factory method for a Commons FileUpload instance.
177: * <p><b>To be implemented by subclasses.</b>
178: * @param fileItemFactory the Commons FileItemFactory to build upon
179: * @return the Commons FileUpload instance
180: */
181: protected abstract FileUpload newFileUpload(
182: FileItemFactory fileItemFactory);
183:
184: /**
185: * Determine an appropriate FileUpload instance for the given encoding.
186: * <p>Default implementation returns the shared FileUpload instance
187: * if the encoding matches, else creates a new FileUpload instance
188: * with the same configuration other than the desired encoding.
189: * @param encoding the character encoding to use
190: * @return an appropriate FileUpload instance.
191: */
192: protected FileUpload prepareFileUpload(String encoding) {
193: FileUpload fileUpload = getFileUpload();
194: FileUpload actualFileUpload = fileUpload;
195:
196: // Use new temporary FileUpload instance if the request specifies
197: // its own encoding that does not match the default encoding.
198: if (encoding != null
199: && !encoding.equals(fileUpload.getHeaderEncoding())) {
200: actualFileUpload = newFileUpload(getFileItemFactory());
201: actualFileUpload.setSizeMax(fileUpload.getSizeMax());
202: actualFileUpload.setHeaderEncoding(encoding);
203: }
204:
205: return actualFileUpload;
206: }
207:
208: /**
209: * Parse the given List of Commons FileItems into a Spring MultipartParsingResult,
210: * containing Spring MultipartFile instances and a Map of multipart parameter.
211: * @param fileItems the Commons FileIterms to parse
212: * @param encoding the encoding to use for form fields
213: * @return the Spring MultipartParsingResult
214: * @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem)
215: */
216: protected MultipartParsingResult parseFileItems(List fileItems,
217: String encoding) {
218: Map multipartFiles = new HashMap();
219: Map multipartParameters = new HashMap();
220:
221: // Extract multipart files and multipart parameters.
222: for (Iterator it = fileItems.iterator(); it.hasNext();) {
223: FileItem fileItem = (FileItem) it.next();
224: if (fileItem.isFormField()) {
225: String value = null;
226: if (encoding != null) {
227: try {
228: value = fileItem.getString(encoding);
229: } catch (UnsupportedEncodingException ex) {
230: if (logger.isWarnEnabled()) {
231: logger
232: .warn("Could not decode multipart item '"
233: + fileItem.getFieldName()
234: + "' with encoding '"
235: + encoding
236: + "': using platform default");
237: }
238: value = fileItem.getString();
239: }
240: } else {
241: value = fileItem.getString();
242: }
243: String[] curParam = (String[]) multipartParameters
244: .get(fileItem.getFieldName());
245: if (curParam == null) {
246: // simple form field
247: multipartParameters.put(fileItem.getFieldName(),
248: new String[] { value });
249: } else {
250: // array of simple form fields
251: String[] newParam = StringUtils.addStringToArray(
252: curParam, value);
253: multipartParameters.put(fileItem.getFieldName(),
254: newParam);
255: }
256: } else {
257: // multipart file field
258: CommonsMultipartFile file = new CommonsMultipartFile(
259: fileItem);
260: if (multipartFiles.put(file.getName(), file) != null) {
261: throw new MultipartException(
262: "Multiple files for field name ["
263: + file.getName()
264: + "] found - not supported by MultipartResolver");
265: }
266: if (logger.isDebugEnabled()) {
267: logger.debug("Found multipart file ["
268: + file.getName() + "] of size "
269: + file.getSize()
270: + " bytes with original filename ["
271: + file.getOriginalFilename() + "], stored "
272: + file.getStorageDescription());
273: }
274: }
275: }
276: return new MultipartParsingResult(multipartFiles,
277: multipartParameters);
278: }
279:
280: /**
281: * Cleanup the Spring MultipartFiles created during multipart parsing,
282: * potentially holding temporary data on disk.
283: * <p>Deletes the underlying Commons FileItem instances.
284: * @param multipartFiles Collection of MultipartFile instances
285: * @see org.apache.commons.fileupload.FileItem#delete()
286: */
287: protected void cleanupFileItems(Collection multipartFiles) {
288: for (Iterator it = multipartFiles.iterator(); it.hasNext();) {
289: CommonsMultipartFile file = (CommonsMultipartFile) it
290: .next();
291: if (logger.isDebugEnabled()) {
292: logger.debug("Cleaning up multipart file ["
293: + file.getName() + "] with original filename ["
294: + file.getOriginalFilename() + "], stored "
295: + file.getStorageDescription());
296: }
297: file.getFileItem().delete();
298: }
299: }
300:
301: /**
302: * Holder for a Map of Spring MultipartFiles and a Map of
303: * multipart parameters.
304: */
305: protected static class MultipartParsingResult {
306:
307: private final Map multipartFiles;
308:
309: private final Map multipartParameters;
310:
311: /**
312: * Create a new MultipartParsingResult.
313: * @param multipartFiles Map of field name to MultipartFile instance
314: * @param multipartParameters Map of field name to form field String value
315: */
316: public MultipartParsingResult(Map multipartFiles,
317: Map multipartParameters) {
318: this .multipartFiles = multipartFiles;
319: this .multipartParameters = multipartParameters;
320: }
321:
322: /**
323: * Return the multipart files as Map of field name to MultipartFile instance.
324: */
325: public Map getMultipartFiles() {
326: return this .multipartFiles;
327: }
328:
329: /**
330: * Return the multipart parameters as Map of field name to form field String value.
331: */
332: public Map getMultipartParameters() {
333: return this.multipartParameters;
334: }
335: }
336:
337: }
|