001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.tomcat.util.http.fileupload;
019:
020: import java.io.BufferedInputStream;
021: import java.io.BufferedOutputStream;
022: import java.io.ByteArrayInputStream;
023: import java.io.File;
024: import java.io.FileInputStream;
025: import java.io.FileOutputStream;
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.OutputStream;
029: import java.io.UnsupportedEncodingException;
030:
031: /**
032: * <p> The default implementation of the
033: * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
034: *
035: * <p> After retrieving an instance of this class from a {@link
036: * org.apache.tomcat.util.http.fileupload.DiskFileUpload DiskFileUpload} instance (see
037: * {@link org.apache.tomcat.util.http.fileupload.DiskFileUpload
038: * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
039: * either request all contents of file at once using {@link #get()} or
040: * request an {@link java.io.InputStream InputStream} with
041: * {@link #getInputStream()} and process the file without attempting to load
042: * it into memory, which may come handy with large files.
043: *
044: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
045: * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
046: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
047: * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
048: * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
049: * @author Sean C. Sullivan
050: *
051: * @version $Id: DefaultFileItem.java 467222 2006-10-24 03:17:11Z markt $
052: */
053: public class DefaultFileItem implements FileItem {
054:
055: // ----------------------------------------------------------- Data members
056:
057: /**
058: * Counter used in unique identifier generation.
059: */
060: private static int counter = 0;
061:
062: /**
063: * The name of the form field as provided by the browser.
064: */
065: private String fieldName;
066:
067: /**
068: * The content type passed by the browser, or <code>null</code> if
069: * not defined.
070: */
071: private String contentType;
072:
073: /**
074: * Whether or not this item is a simple form field.
075: */
076: private boolean isFormField;
077:
078: /**
079: * The original filename in the user's filesystem.
080: */
081: private String fileName;
082:
083: /**
084: * The threshold above which uploads will be stored on disk.
085: */
086: private int sizeThreshold;
087:
088: /**
089: * The directory in which uploaded files will be stored, if stored on disk.
090: */
091: private File repository;
092:
093: /**
094: * Cached contents of the file.
095: */
096: private byte[] cachedContent;
097:
098: /**
099: * Output stream for this item.
100: */
101: private DeferredFileOutputStream dfos;
102:
103: // ----------------------------------------------------------- Constructors
104:
105: /**
106: * Constructs a new <code>DefaultFileItem</code> instance.
107: *
108: * @param fieldName The name of the form field.
109: * @param contentType The content type passed by the browser or
110: * <code>null</code> if not specified.
111: * @param isFormField Whether or not this item is a plain form field, as
112: * opposed to a file upload.
113: * @param fileName The original filename in the user's filesystem, or
114: * <code>null</code> if not specified.
115: * @param sizeThreshold The threshold, in bytes, below which items will be
116: * retained in memory and above which they will be
117: * stored as a file.
118: * @param repository The data repository, which is the directory in
119: * which files will be created, should the item size
120: * exceed the threshold.
121: */
122: DefaultFileItem(String fieldName, String contentType,
123: boolean isFormField, String fileName, int sizeThreshold,
124: File repository) {
125: this .fieldName = fieldName;
126: this .contentType = contentType;
127: this .isFormField = isFormField;
128: this .fileName = fileName;
129: this .sizeThreshold = sizeThreshold;
130: this .repository = repository;
131: }
132:
133: // ------------------------------- Methods from javax.activation.DataSource
134:
135: /**
136: * Returns an {@link java.io.InputStream InputStream} that can be
137: * used to retrieve the contents of the file.
138: *
139: * @return An {@link java.io.InputStream InputStream} that can be
140: * used to retrieve the contents of the file.
141: *
142: * @exception IOException if an error occurs.
143: */
144: public InputStream getInputStream() throws IOException {
145: if (!dfos.isInMemory()) {
146: return new FileInputStream(dfos.getFile());
147: }
148:
149: if (cachedContent == null) {
150: cachedContent = dfos.getData();
151: }
152: return new ByteArrayInputStream(cachedContent);
153: }
154:
155: /**
156: * Returns the content type passed by the browser or <code>null</code> if
157: * not defined.
158: *
159: * @return The content type passed by the browser or <code>null</code> if
160: * not defined.
161: */
162: public String getContentType() {
163: return contentType;
164: }
165:
166: /**
167: * Returns the original filename in the client's filesystem.
168: *
169: * @return The original filename in the client's filesystem.
170: */
171: public String getName() {
172: return fileName;
173: }
174:
175: // ------------------------------------------------------- FileItem methods
176:
177: /**
178: * Provides a hint as to whether or not the file contents will be read
179: * from memory.
180: *
181: * @return <code>true</code> if the file contents will be read
182: * from memory; <code>false</code> otherwise.
183: */
184: public boolean isInMemory() {
185: return (dfos.isInMemory());
186: }
187:
188: /**
189: * Returns the size of the file.
190: *
191: * @return The size of the file, in bytes.
192: */
193: public long getSize() {
194: if (cachedContent != null) {
195: return cachedContent.length;
196: } else if (dfos.isInMemory()) {
197: return dfos.getData().length;
198: } else {
199: return dfos.getFile().length();
200: }
201: }
202:
203: /**
204: * Returns the contents of the file as an array of bytes. If the
205: * contents of the file were not yet cached in memory, they will be
206: * loaded from the disk storage and cached.
207: *
208: * @return The contents of the file as an array of bytes.
209: */
210: public byte[] get() {
211: if (dfos.isInMemory()) {
212: if (cachedContent == null) {
213: cachedContent = dfos.getData();
214: }
215: return cachedContent;
216: }
217:
218: byte[] fileData = new byte[(int) getSize()];
219: FileInputStream fis = null;
220:
221: try {
222: fis = new FileInputStream(dfos.getFile());
223: fis.read(fileData);
224: } catch (IOException e) {
225: fileData = null;
226: } finally {
227: if (fis != null) {
228: try {
229: fis.close();
230: } catch (IOException e) {
231: // ignore
232: }
233: }
234: }
235:
236: return fileData;
237: }
238:
239: /**
240: * Returns the contents of the file as a String, using the specified
241: * encoding. This method uses {@link #get()} to retrieve the
242: * contents of the file.
243: *
244: * @param encoding The character encoding to use.
245: *
246: * @return The contents of the file, as a string.
247: *
248: * @exception UnsupportedEncodingException if the requested character
249: * encoding is not available.
250: */
251: public String getString(String encoding)
252: throws UnsupportedEncodingException {
253: return new String(get(), encoding);
254: }
255:
256: /**
257: * Returns the contents of the file as a String, using the default
258: * character encoding. This method uses {@link #get()} to retrieve the
259: * contents of the file.
260: *
261: * @return The contents of the file, as a string.
262: */
263: public String getString() {
264: return new String(get());
265: }
266:
267: /**
268: * A convenience method to write an uploaded item to disk. The client code
269: * is not concerned with whether or not the item is stored in memory, or on
270: * disk in a temporary location. They just want to write the uploaded item
271: * to a file.
272: * <p>
273: * This implementation first attempts to rename the uploaded item to the
274: * specified destination file, if the item was originally written to disk.
275: * Otherwise, the data will be copied to the specified file.
276: * <p>
277: * This method is only guaranteed to work <em>once</em>, the first time it
278: * is invoked for a particular item. This is because, in the event that the
279: * method renames a temporary file, that file will no longer be available
280: * to copy or rename again at a later time.
281: *
282: * @param file The <code>File</code> into which the uploaded item should
283: * be stored.
284: *
285: * @exception Exception if an error occurs.
286: */
287: public void write(File file) throws Exception {
288: if (isInMemory()) {
289: FileOutputStream fout = null;
290: try {
291: fout = new FileOutputStream(file);
292: fout.write(get());
293: } finally {
294: if (fout != null) {
295: fout.close();
296: }
297: }
298: } else {
299: File outputFile = getStoreLocation();
300: if (outputFile != null) {
301: /*
302: * The uploaded file is being stored on disk
303: * in a temporary location so move it to the
304: * desired file.
305: */
306: if (!outputFile.renameTo(file)) {
307: BufferedInputStream in = null;
308: BufferedOutputStream out = null;
309: try {
310: in = new BufferedInputStream(
311: new FileInputStream(outputFile));
312: out = new BufferedOutputStream(
313: new FileOutputStream(file));
314: byte[] bytes = new byte[2048];
315: int s = 0;
316: while ((s = in.read(bytes)) != -1) {
317: out.write(bytes, 0, s);
318: }
319: } finally {
320: try {
321: in.close();
322: } catch (IOException e) {
323: // ignore
324: }
325: try {
326: out.close();
327: } catch (IOException e) {
328: // ignore
329: }
330: }
331: }
332: } else {
333: /*
334: * For whatever reason we cannot write the
335: * file to disk.
336: */
337: throw new FileUploadException(
338: "Cannot write uploaded file to disk!");
339: }
340: }
341: }
342:
343: /**
344: * Deletes the underlying storage for a file item, including deleting any
345: * associated temporary disk file. Although this storage will be deleted
346: * automatically when the <code>FileItem</code> instance is garbage
347: * collected, this method can be used to ensure that this is done at an
348: * earlier time, thus preserving system resources.
349: */
350: public void delete() {
351: cachedContent = null;
352: File outputFile = getStoreLocation();
353: if (outputFile != null && outputFile.exists()) {
354: outputFile.delete();
355: }
356: }
357:
358: /**
359: * Returns the name of the field in the multipart form corresponding to
360: * this file item.
361: *
362: * @return The name of the form field.
363: *
364: * @see #setFieldName(java.lang.String)
365: *
366: */
367: public String getFieldName() {
368: return fieldName;
369: }
370:
371: /**
372: * Sets the field name used to reference this file item.
373: *
374: * @param fieldName The name of the form field.
375: *
376: * @see #getFieldName()
377: *
378: */
379: public void setFieldName(String fieldName) {
380: this .fieldName = fieldName;
381: }
382:
383: /**
384: * Determines whether or not a <code>FileItem</code> instance represents
385: * a simple form field.
386: *
387: * @return <code>true</code> if the instance represents a simple form
388: * field; <code>false</code> if it represents an uploaded file.
389: *
390: * @see #setFormField(boolean)
391: *
392: */
393: public boolean isFormField() {
394: return isFormField;
395: }
396:
397: /**
398: * Specifies whether or not a <code>FileItem</code> instance represents
399: * a simple form field.
400: *
401: * @param state <code>true</code> if the instance represents a simple form
402: * field; <code>false</code> if it represents an uploaded file.
403: *
404: * @see #isFormField()
405: *
406: */
407: public void setFormField(boolean state) {
408: isFormField = state;
409: }
410:
411: /**
412: * Returns an {@link java.io.OutputStream OutputStream} that can
413: * be used for storing the contents of the file.
414: *
415: * @return An {@link java.io.OutputStream OutputStream} that can be used
416: * for storing the contensts of the file.
417: *
418: * @exception IOException if an error occurs.
419: */
420: public OutputStream getOutputStream() throws IOException {
421: if (dfos == null) {
422: File outputFile = getTempFile();
423: dfos = new DeferredFileOutputStream(sizeThreshold,
424: outputFile);
425: }
426: return dfos;
427: }
428:
429: // --------------------------------------------------------- Public methods
430:
431: /**
432: * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
433: * data's temporary location on the disk. Note that for
434: * <code>FileItem</code>s that have their data stored in memory,
435: * this method will return <code>null</code>. When handling large
436: * files, you can use {@link java.io.File#renameTo(java.io.File)} to
437: * move the file to new location without copying the data, if the
438: * source and destination locations reside within the same logical
439: * volume.
440: *
441: * @return The data file, or <code>null</code> if the data is stored in
442: * memory.
443: */
444: public File getStoreLocation() {
445: return dfos.getFile();
446: }
447:
448: // ------------------------------------------------------ Protected methods
449:
450: /**
451: * Removes the file contents from the temporary storage.
452: */
453: protected void finalize() {
454: File outputFile = dfos.getFile();
455:
456: if (outputFile != null && outputFile.exists()) {
457: outputFile.delete();
458: }
459: }
460:
461: /**
462: * Creates and returns a {@link java.io.File File} representing a uniquely
463: * named temporary file in the configured repository path.
464: *
465: * @return The {@link java.io.File File} to be used for temporary storage.
466: */
467: protected File getTempFile() {
468: File tempDir = repository;
469: if (tempDir == null) {
470: tempDir = new File(System.getProperty("java.io.tmpdir"));
471: }
472:
473: String fileName = "upload_" + getUniqueId() + ".tmp";
474:
475: File f = new File(tempDir, fileName);
476: f.deleteOnExit();
477: return f;
478: }
479:
480: // -------------------------------------------------------- Private methods
481:
482: /**
483: * Returns an identifier that is unique within the class loader used to
484: * load this class, but does not have random-like apearance.
485: *
486: * @return A String with the non-random looking instance identifier.
487: */
488: private static String getUniqueId() {
489: int current;
490: synchronized (DefaultFileItem.class) {
491: current = counter++;
492: }
493: String id = Integer.toString(current);
494:
495: // If you manage to get more than 100 million of ids, you'll
496: // start getting ids longer than 8 characters.
497: if (current < 100000000) {
498: id = ("00000000" + id).substring(id.length());
499: }
500: return id;
501: }
502:
503: }
|