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