KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > struts > upload > CommonsMultipartRequestHandler


1 /*
2  * $Id: CommonsMultipartRequestHandler.java 54929 2004-10-16 16:38:42Z germuska $
3  *
4  * Copyright 1999-2004 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18
19
20 package org.apache.struts.upload;
21
22
23 import java.io.File JavaDoc;
24 import java.io.FileNotFoundException JavaDoc;
25 import java.io.InputStream JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.io.Serializable JavaDoc;
28 import java.util.Hashtable JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
31 import javax.servlet.ServletContext JavaDoc;
32 import javax.servlet.ServletException JavaDoc;
33 import javax.servlet.http.HttpServletRequest JavaDoc;
34 import org.apache.commons.fileupload.FileItem;
35 import org.apache.commons.fileupload.DiskFileUpload;
36 import org.apache.commons.fileupload.FileUploadException;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.apache.struts.action.ActionServlet;
40 import org.apache.struts.action.ActionMapping;
41 import org.apache.struts.config.ModuleConfig;
42 import org.apache.struts.Globals;
43
44
45  /**
46   * This class implements the <code>MultipartRequestHandler</code> interface
47   * by providing a wrapper around the Jakarta Commons FileUpload library.
48   *
49   * @version $Rev: 54929 $ $Date: 2004-10-16 17:38:42 +0100 (Sat, 16 Oct 2004) $
50   * @since Struts 1.1
51   */

52 public class CommonsMultipartRequestHandler implements MultipartRequestHandler {
53
54
55     // ----------------------------------------------------- Manifest Constants
56

57
58     /**
59      * The default value for the maximum allowable size, in bytes, of an
60      * uploaded file. The value is equivalent to 250MB.
61      */

62     public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;
63
64
65     /**
66      * The default value for the threshold which determines whether an uploaded
67      * file will be written to disk or cached in memory. The value is equivalent
68      * to 250KB.
69      */

70     public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;
71
72
73     // ----------------------------------------------------- Instance Variables
74

75
76     /**
77      * Commons Logging instance.
78      */

79     protected static Log log = LogFactory.getLog(
80             CommonsMultipartRequestHandler.class);
81
82
83     /**
84      * The combined text and file request parameters.
85      */

86     private Hashtable JavaDoc elementsAll;
87
88
89     /**
90      * The file request parameters.
91      */

92     private Hashtable JavaDoc elementsFile;
93
94
95     /**
96      * The text request parameters.
97      */

98     private Hashtable JavaDoc elementsText;
99
100
101     /**
102      * The action mapping with which this handler is associated.
103      */

104     private ActionMapping mapping;
105
106
107     /**
108      * The servlet with which this handler is associated.
109      */

110     private ActionServlet servlet;
111
112
113     // ---------------------------------------- MultipartRequestHandler Methods
114

115
116     /**
117      * Retrieves the servlet with which this handler is associated.
118      *
119      * @return The associated servlet.
120      */

121     public ActionServlet getServlet() {
122         return this.servlet;
123     }
124
125
126     /**
127      * Sets the servlet with which this handler is associated.
128      *
129      * @param servlet The associated servlet.
130      */

131     public void setServlet(ActionServlet servlet) {
132         this.servlet = servlet;
133     }
134
135
136     /**
137      * Retrieves the action mapping with which this handler is associated.
138      *
139      * @return The associated action mapping.
140      */

141     public ActionMapping getMapping() {
142         return this.mapping;
143     }
144
145
146     /**
147      * Sets the action mapping with which this handler is associated.
148      *
149      * @param mapping The associated action mapping.
150      */

151     public void setMapping(ActionMapping mapping) {
152         this.mapping = mapping;
153     }
154
155
156     /**
157      * Parses the input stream and partitions the parsed items into a set of
158      * form fields and a set of file items. In the process, the parsed items
159      * are translated from Commons FileUpload <code>FileItem</code> instances
160      * to Struts <code>FormFile</code> instances.
161      *
162      * @param request The multipart request to be processed.
163      *
164      * @throws ServletException if an unrecoverable error occurs.
165      */

166     public void handleRequest(HttpServletRequest JavaDoc request)
167             throws ServletException JavaDoc {
168
169         // Get the app config for the current request.
170
ModuleConfig ac = (ModuleConfig) request.getAttribute(
171                 Globals.MODULE_KEY);
172
173         // Create and configure a DIskFileUpload instance.
174
DiskFileUpload upload = new DiskFileUpload();
175         // The following line is to support an "EncodingFilter"
176
// see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=23255
177
upload.setHeaderEncoding(request.getCharacterEncoding());
178         // Set the maximum size before a FileUploadException will be thrown.
179
upload.setSizeMax(getSizeMax(ac));
180         // Set the maximum size that will be stored in memory.
181
upload.setSizeThreshold((int) getSizeThreshold(ac));
182         // Set the the location for saving data on disk.
183
upload.setRepositoryPath(getRepositoryPath(ac));
184
185         // Create the hash tables to be populated.
186
elementsText = new Hashtable JavaDoc();
187         elementsFile = new Hashtable JavaDoc();
188         elementsAll = new Hashtable JavaDoc();
189
190         // Parse the request into file items.
191
List JavaDoc items = null;
192         try {
193             items = upload.parseRequest(request);
194         } catch (DiskFileUpload.SizeLimitExceededException e) {
195             // Special handling for uploads that are too big.
196
request.setAttribute(
197                     MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,
198                     Boolean.TRUE);
199             return;
200         } catch (FileUploadException e) {
201             log.error("Failed to parse multipart request", e);
202             throw new ServletException JavaDoc(e);
203         }
204
205         // Partition the items into form fields and files.
206
Iterator JavaDoc iter = items.iterator();
207         while (iter.hasNext()) {
208             FileItem item = (FileItem) iter.next();
209
210             if (item.isFormField()) {
211                 addTextParameter(request, item);
212             } else {
213                 addFileParameter(item);
214             }
215         }
216     }
217
218
219     /**
220      * Returns a hash table containing the text (that is, non-file) request
221      * parameters.
222      *
223      * @return The text request parameters.
224      */

225     public Hashtable JavaDoc getTextElements() {
226         return this.elementsText;
227     }
228
229
230     /**
231      * Returns a hash table containing the file (that is, non-text) request
232      * parameters.
233      *
234      * @return The file request parameters.
235      */

236     public Hashtable JavaDoc getFileElements() {
237         return this.elementsFile;
238     }
239
240
241     /**
242      * Returns a hash table containing both text and file request parameters.
243      *
244      * @return The text and file request parameters.
245      */

246     public Hashtable JavaDoc getAllElements() {
247         return this.elementsAll;
248     }
249
250
251     /**
252      * Cleans up when a problem occurs during request processing.
253      */

254     public void rollback() {
255         Iterator JavaDoc iter = elementsFile.values().iterator();
256
257         while (iter.hasNext()) {
258             FormFile formFile = (FormFile) iter.next();
259
260             formFile.destroy();
261         }
262     }
263
264
265     /**
266      * Cleans up at the end of a request.
267      */

268     public void finish() {
269         rollback();
270     }
271
272
273     // -------------------------------------------------------- Support Methods
274

275
276     /**
277      * Returns the maximum allowable size, in bytes, of an uploaded file. The
278      * value is obtained from the current module's controller configuration.
279      *
280      * @param mc The current module's configuration.
281      *
282      * @return The maximum allowable file size, in bytes.
283      */

284     protected long getSizeMax(ModuleConfig mc) {
285         return convertSizeToBytes(
286                 mc.getControllerConfig().getMaxFileSize(),
287                 DEFAULT_SIZE_MAX);
288     }
289
290
291     /**
292      * Returns the size threshold which determines whether an uploaded file
293      * will be written to disk or cached in memory.
294      *
295      * @param mc The current module's configuration.
296      *
297      * @return The size threshold, in bytes.
298      */

299     protected long getSizeThreshold(ModuleConfig mc) {
300         return convertSizeToBytes(
301                 mc.getControllerConfig().getMemFileSize(),
302                 DEFAULT_SIZE_THRESHOLD);
303     }
304
305     /**
306      * Converts a size value from a string representation to its numeric value.
307      * The string must be of the form nnnm, where nnn is an arbitrary decimal
308      * value, and m is a multiplier. The multiplier must be one of 'K', 'M' and
309      * 'G', representing kilobytes, megabytes and gigabytes respectively.
310      *
311      * If the size value cannot be converted, for example due to invalid syntax,
312      * the supplied default is returned instead.
313      *
314      * @param sizeString The string representation of the size to be converted.
315      * @param defaultSize The value to be returned if the string is invalid.
316      *
317      * @return The actual size in bytes.
318      */

319     protected long convertSizeToBytes(String JavaDoc sizeString, long defaultSize) {
320         int multiplier = 1;
321
322         if (sizeString.endsWith("K")) {
323             multiplier = 1024;
324         } else if (sizeString.endsWith("M")) {
325             multiplier = 1024 * 1024;
326         } else if (sizeString.endsWith("G")) {
327             multiplier = 1024 * 1024 * 1024;
328         }
329         if (multiplier != 1) {
330             sizeString = sizeString.substring(0, sizeString.length() - 1);
331         }
332         
333         long size = 0;
334         try {
335             size = Long.parseLong(sizeString);
336         } catch (NumberFormatException JavaDoc nfe) {
337             log.warn("Invalid format for file size ('" + sizeString +
338                     "'). Using default.");
339             size = defaultSize;
340             multiplier = 1;
341         }
342                 
343         return (size * multiplier);
344     }
345
346
347     /**
348      * Returns the path to the temporary directory to be used for uploaded
349      * files which are written to disk. The directory used is determined from
350      * the first of the following to be non-empty.
351      * <ol>
352      * <li>A temp dir explicitly defined either using the <code>tempDir</code>
353      * servlet init param, or the <code>tempDir</code> attribute of the
354      * &lt;controller&gt; element in the Struts config file.</li>
355      * <li>The container-specified temp dir, obtained from the
356      * <code>javax.servlet.context.tempdir</code> servlet context
357      * attribute.</li>
358      * <li>The temp dir specified by the <code>java.io.tmpdir</code> system
359      * property.</li>
360      * (/ol>
361      *
362      * @param mc The module config instance for which the path should be
363      * determined.
364      *
365      * @return The path to the directory to be used to store uploaded files.
366      */

367     protected String JavaDoc getRepositoryPath(ModuleConfig mc) {
368
369         // First, look for an explicitly defined temp dir.
370
String JavaDoc tempDir = mc.getControllerConfig().getTempDir();
371
372         // If none, look for a container specified temp dir.
373
if (tempDir == null || tempDir.length() == 0) {
374             if (servlet != null) {
375                 ServletContext JavaDoc context = servlet.getServletContext();
376                 File JavaDoc tempDirFile = (File JavaDoc) context.getAttribute(
377                         "javax.servlet.context.tempdir");
378                 tempDir = tempDirFile.getAbsolutePath();
379             }
380
381             // If none, pick up the system temp dir.
382
if (tempDir == null || tempDir.length() == 0) {
383                 tempDir = System.getProperty("java.io.tmpdir");
384             }
385         }
386
387         if (log.isTraceEnabled()) {
388             log.trace("File upload temp dir: " + tempDir);
389         }
390
391         return tempDir;
392     }
393
394
395     /**
396      * Adds a regular text parameter to the set of text parameters for this
397      * request and also to the list of all parameters. Handles the case of
398      * multiple values for the same parameter by using an array for the
399      * parameter value.
400      *
401      * @param request The request in which the parameter was specified.
402      * @param item The file item for the parameter to add.
403      */

404     protected void addTextParameter(HttpServletRequest JavaDoc request, FileItem item) {
405         String JavaDoc name = item.getFieldName();
406         String JavaDoc value = null;
407         boolean haveValue = false;
408         String JavaDoc encoding = request.getCharacterEncoding();
409
410         if (encoding != null) {
411             try {
412                 value = item.getString(encoding);
413                 haveValue = true;
414             } catch (Exception JavaDoc e) {
415                 // Handled below, since haveValue is false.
416
}
417         }
418         if (!haveValue) {
419             try {
420                  value = item.getString("ISO-8859-1");
421             } catch (java.io.UnsupportedEncodingException JavaDoc uee) {
422                  value = item.getString();
423             }
424             haveValue = true;
425         }
426
427         if (request instanceof MultipartRequestWrapper) {
428             MultipartRequestWrapper wrapper = (MultipartRequestWrapper) request;
429             wrapper.setParameter(name, value);
430         }
431
432         String JavaDoc[] oldArray = (String JavaDoc[]) elementsText.get(name);
433         String JavaDoc[] newArray;
434
435         if (oldArray != null) {
436             newArray = new String JavaDoc[oldArray.length + 1];
437             System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
438             newArray[oldArray.length] = value;
439         } else {
440             newArray = new String JavaDoc[] { value };
441         }
442
443         elementsText.put(name, newArray);
444         elementsAll.put(name, newArray);
445     }
446
447
448     /**
449      * Adds a file parameter to the set of file parameters for this request
450      * and also to the list of all parameters.
451      *
452      * @param item The file item for the parameter to add.
453      */

454     protected void addFileParameter(FileItem item) {
455         FormFile formFile = new CommonsFormFile(item);
456
457         elementsFile.put(item.getFieldName(), formFile);
458         elementsAll.put(item.getFieldName(), formFile);
459     }
460
461
462     // ---------------------------------------------------------- Inner Classes
463

464
465     /**
466      * This class implements the Struts <code>FormFile</code> interface by
467      * wrapping the Commons FileUpload <code>FileItem</code> interface. This
468      * implementation is <i>read-only</i>; any attempt to modify an instance
469      * of this class will result in an <code>UnsupportedOperationException</code>.
470      */

471     static class CommonsFormFile implements FormFile, Serializable JavaDoc {
472
473         /**
474          * The <code>FileItem</code> instance wrapped by this object.
475          */

476         FileItem fileItem;
477
478
479         /**
480          * Constructs an instance of this class which wraps the supplied
481          * file item.
482          *
483          * @param fileItem The Commons file item to be wrapped.
484          */

485         public CommonsFormFile(FileItem fileItem) {
486             this.fileItem = fileItem;
487         }
488
489
490         /**
491          * Returns the content type for this file.
492          *
493          * @return A String representing content type.
494          */

495         public String JavaDoc getContentType() {
496             return fileItem.getContentType();
497         }
498
499
500         /**
501          * Sets the content type for this file.
502          * <p>
503          * NOTE: This method is not supported in this implementation.
504          *
505          * @param contentType A string representing the content type.
506          */

507         public void setContentType(String JavaDoc contentType) {
508             throw new UnsupportedOperationException JavaDoc(
509                     "The setContentType() method is not supported.");
510         }
511
512
513         /**
514          * Returns the size, in bytes, of this file.
515          *
516          * @return The size of the file, in bytes.
517          */

518         public int getFileSize() {
519             return (int)fileItem.getSize();
520         }
521
522
523         /**
524          * Sets the size, in bytes, for this file.
525          * <p>
526          * NOTE: This method is not supported in this implementation.
527          *
528          * @param filesize The size of the file, in bytes.
529          */

530         public void setFileSize(int filesize) {
531             throw new UnsupportedOperationException JavaDoc(
532                     "The setFileSize() method is not supported.");
533         }
534
535
536         /**
537          * Returns the (client-side) file name for this file.
538          *
539          * @return The client-size file name.
540          */

541         public String JavaDoc getFileName() {
542             return getBaseFileName(fileItem.getName());
543         }
544
545
546         /**
547          * Sets the (client-side) file name for this file.
548          * <p>
549          * NOTE: This method is not supported in this implementation.
550          *
551          * @param fileName The client-side name for the file.
552          */

553         public void setFileName(String JavaDoc fileName) {
554             throw new UnsupportedOperationException JavaDoc(
555                     "The setFileName() method is not supported.");
556         }
557
558
559         /**
560          * Returns the data for this file as a byte array. Note that this may
561          * result in excessive memory usage for large uploads. The use of the
562          * {@link #getInputStream() getInputStream} method is encouraged
563          * as an alternative.
564          *
565          * @return An array of bytes representing the data contained in this
566          * form file.
567          *
568          * @exception FileNotFoundException If some sort of file representation
569          * cannot be found for the FormFile
570          * @exception IOException If there is some sort of IOException
571          */

572         public byte[] getFileData() throws FileNotFoundException JavaDoc, IOException JavaDoc {
573             return fileItem.get();
574         }
575
576
577         /**
578          * Get an InputStream that represents this file. This is the preferred
579          * method of getting file data.
580          * @exception FileNotFoundException If some sort of file representation
581          * cannot be found for the FormFile
582          * @exception IOException If there is some sort of IOException
583          */

584         public InputStream JavaDoc getInputStream() throws FileNotFoundException JavaDoc, IOException JavaDoc {
585             return fileItem.getInputStream();
586         }
587
588
589         /**
590          * Destroy all content for this form file.
591          * Implementations should remove any temporary
592          * files or any temporary file data stored somewhere
593          */

594         public void destroy() {
595             fileItem.delete();
596         }
597
598
599         /**
600          * Returns the base file name from the supplied file path. On the surface,
601          * this would appear to be a trivial task. Apparently, however, some Linux
602          * JDKs do not implement <code>File.getName()</code> correctly for Windows
603          * paths, so we attempt to take care of that here.
604          *
605          * @param filePath The full path to the file.
606          *
607          * @return The base file name, from the end of the path.
608          */

609         protected String JavaDoc getBaseFileName(String JavaDoc filePath) {
610
611             // First, ask the JDK for the base file name.
612
String JavaDoc fileName = new File JavaDoc(filePath).getName();
613
614             // Now check for a Windows file name parsed incorrectly.
615
int colonIndex = fileName.indexOf(":");
616             if (colonIndex == -1) {
617                 // Check for a Windows SMB file path.
618
colonIndex = fileName.indexOf("\\\\");
619             }
620             int backslashIndex = fileName.lastIndexOf("\\");
621
622             if (colonIndex > -1 && backslashIndex > -1) {
623                 // Consider this filename to be a full Windows path, and parse it
624
// accordingly to retrieve just the base file name.
625
fileName = fileName.substring(backslashIndex + 1);
626             }
627
628             return fileName;
629         }
630
631         /**
632          * Returns the (client-side) file name for this file.
633          *
634          * @return The client-size file name.
635          */

636         public String JavaDoc toString() {
637             return getFileName();
638         }
639     }
640 }
641
Popular Tags