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