Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/apache/struts/upload/CommonsMultipartRequestHandler.java


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;
24  import java.io.FileNotFoundException;
25  import java.io.InputStream;
26  import java.io.IOException;
27  import java.io.Serializable;
28  import java.util.Hashtable;
29  import java.util.Iterator;
30  import java.util.List;
31  import javax.servlet.ServletContext;
32  import javax.servlet.ServletException;
33  import javax.servlet.http.HttpServletRequest;
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 09:38:42 -0700 (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 elementsAll;
87  
88  
89      /**
90       * The file request parameters.
91       */
92      private Hashtable elementsFile;
93  
94  
95      /**
96       * The text request parameters.
97       */
98      private Hashtable 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 request)
167             throws ServletException {
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();
187         elementsFile = new Hashtable();
188         elementsAll = new Hashtable();
189 
190         // Parse the request into file items.
191         List 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(e);
203         }
204 
205         // Partition the items into form fields and files.
206         Iterator 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 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 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 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 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 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 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 getRepositoryPath(ModuleConfig mc) {
368 
369         // First, look for an explicitly defined temp dir.
370         String 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 context = servlet.getServletContext();
376                 File tempDirFile = (File) 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 request, FileItem item) {
405         String name = item.getFieldName();
406         String value = null;
407         boolean haveValue = false;
408         String encoding = request.getCharacterEncoding();
409 
410         if (encoding != null) {
411             try {
412                 value = item.getString(encoding);
413                 haveValue = true;
414             } catch (Exception 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 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[] oldArray = (String[]) elementsText.get(name);
433         String[] newArray;
434 
435         if (oldArray != null) {
436             newArray = new String[oldArray.length + 1];
437             System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
438             newArray[oldArray.length] = value;
439         } else {
440             newArray = new String[] { 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 {
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 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 contentType) {
508             throw new UnsupportedOperationException(
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(
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 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 fileName) {
554             throw new UnsupportedOperationException(
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, IOException {
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 getInputStream() throws FileNotFoundException, IOException {
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 getBaseFileName(String filePath) {
610 
611             // First, ask the JDK for the base file name.
612             String fileName = new File(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 toString() {
637             return getFileName();
638         }
639     }
640 }