2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
17
package org.apache.commons.fileupload.disk;
19
import java.io.BufferedInputStream;
20
import java.io.BufferedOutputStream;
21
import java.io.ByteArrayInputStream;
23
import java.io.FileInputStream;
24
import java.io.FileOutputStream;
25
import java.io.IOException;
26
import java.io.InputStream;
27
import java.io.OutputStream;
28
import java.io.ObjectOutputStream;
29
import java.io.ObjectInputStream;
30
import java.io.UnsupportedEncodingException;
32
import org.apache.commons.io.IOUtils;
33
import org.apache.commons.io.FileCleaner;
34
import org.apache.commons.io.output.DeferredFileOutputStream;
36
import org.apache.commons.fileupload.FileItem;
37
import org.apache.commons.fileupload.FileUploadException;
38
import org.apache.commons.fileupload.ParameterParser;
42
* <p> The default implementation of the
43
* {@link org.apache.commons.fileupload.FileItem FileItem} interface.
45
* <p> After retrieving an instance of this class from a {@link
46
* org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
47
* {@link org.apache.commons.fileupload.DiskFileUpload
48
* #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
49
* either request all contents of file at once using {@link #get()} or
50
* request an {@link java.io.InputStream InputStream} with
51
* {@link #getInputStream()} and process the file without attempting to load
52
* it into memory, which may come handy with large files.
54
* <p>When using the <code>DiskFileItemFactory</code>, then you should
55
* consider the following: Temporary files are automatically deleted as
56
* soon as they are no longer needed. (More precisely, when the
57
* corresponding instance of {@link java.io.File} is garbage collected.)
58
* This is done by the so-called reaper thread, which is started
59
* automatically when the class {@link FileCleaner} is loaded.
60
* It might make sense to terminate that thread, for example, if
61
* your web application ends. See the section on "Resource cleanup"
62
* in the users guide of commons-fileupload.</p>
64
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
65
* @author <a href="mailto:sean@informage.net">Sean Legassick</a>
66
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
67
* @author <a href="mailto:jmcnally@apache.org">John McNally</a>
68
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
69
* @author Sean C. Sullivan
71
* @since FileUpload 1.1
73
* @version $Id: DiskFileItem.java 482993 2006-12-06 09:42:01Z jochen $
75
public class DiskFileItem
78
// ----------------------------------------------------- Manifest constants
82
* Default content charset to be used when no explicit charset
83
* parameter is provided by the sender. Media subtypes of the
84
* "text" type are defined to have a default charset value of
85
* "ISO-8859-1" when received via HTTP.
87
public static final String DEFAULT_CHARSET = "ISO-8859-1";
90
// ----------------------------------------------------------- Data members
94
* UID used in unique file name generation.
96
private static final String UID =
97
new java.rmi.server.UID().toString()
98
.replace(':', '_').replace('-', '_');
101
* Counter used in unique identifier generation.
103
private static int counter = 0;
107
* The name of the form field as provided by the browser.
109
private String fieldName;
113
* The content type passed by the browser, or <code>null</code> if
116
private String contentType;
120
* Whether or not this item is a simple form field.
122
private boolean isFormField;
126
* The original filename in the user's filesystem.
128
private String fileName;
132
* The size of the item, in bytes. This is used to cache the size when a
133
* file item is moved from its original location.
135
private long size = -1;
139
* The threshold above which uploads will be stored on disk.
141
private int sizeThreshold;
145
* The directory in which uploaded files will be stored, if stored on disk.
147
private File repository;
151
* Cached contents of the file.
153
private byte[] cachedContent;
157
* Output stream for this item.
159
private transient DeferredFileOutputStream dfos;
162
* File to allow for serialization of the content of this item.
164
private File dfosFile;
167
// ----------------------------------------------------------- Constructors
171
* Constructs a new <code>DiskFileItem</code> instance.
173
* @param fieldName The name of the form field.
174
* @param contentType The content type passed by the browser or
175
* <code>null</code> if not specified.
176
* @param isFormField Whether or not this item is a plain form field, as
177
* opposed to a file upload.
178
* @param fileName The original filename in the user's filesystem, or
179
* <code>null</code> if not specified.
180
* @param sizeThreshold The threshold, in bytes, below which items will be
181
* retained in memory and above which they will be
183
* @param repository The data repository, which is the directory in
184
* which files will be created, should the item size
185
* exceed the threshold.
187
public DiskFileItem(String fieldName, String contentType,
188
boolean isFormField, String fileName, int sizeThreshold,
190
this.fieldName = fieldName;
191
this.contentType = contentType;
192
this.isFormField = isFormField;
193
this.fileName = fileName;
194
this.sizeThreshold = sizeThreshold;
195
this.repository = repository;
199
// ------------------------------- Methods from javax.activation.DataSource
203
* Returns an {@link java.io.InputStream InputStream} that can be
204
* used to retrieve the contents of the file.
206
* @return An {@link java.io.InputStream InputStream} that can be
207
* used to retrieve the contents of the file.
209
* @throws IOException if an error occurs.
211
public InputStream getInputStream()
214
return new FileInputStream(dfos.getFile());
217
if (cachedContent == null) {
218
cachedContent = dfos.getData();
220
return new ByteArrayInputStream(cachedContent);
225
* Returns the content type passed by the agent or <code>null</code> if
228
* @return The content type passed by the agent or <code>null</code> if
231
public String getContentType() {
237
* Returns the content charset passed by the agent or <code>null</code> if
240
* @return The content charset passed by the agent or <code>null</code> if
243
public String getCharSet() {
244
ParameterParser parser = new ParameterParser();
245
parser.setLowerCaseNames(true);
246
// Parameter parser can handle null input
247
Map params = parser.parse(getContentType(), ';');
248
return (String) params.get("charset");
253
* Returns the original filename in the client's filesystem.
255
* @return The original filename in the client's filesystem.
257
public String getName() {
262
// ------------------------------------------------------- FileItem methods
266
* Provides a hint as to whether or not the file contents will be read
269
* @return <code>true</code> if the file contents will be read
270
* from memory; <code>false</code> otherwise.
272
public boolean isInMemory() {
273
if (cachedContent != null) {
276
return dfos.isInMemory();
281
* Returns the size of the file.
283
* @return The size of the file, in bytes.
285
public long getSize() {
288
} else if (cachedContent != null) {
289
return cachedContent.length;
290
} else if (dfos.isInMemory()) {
291
return dfos.getData().length;
293
return dfos.getFile().length();
299
* Returns the contents of the file as an array of bytes. If the
300
* contents of the file were not yet cached in memory, they will be
301
* loaded from the disk storage and cached.
303
* @return The contents of the file as an array of bytes.
305
public byte[] get() {
307
if (cachedContent == null) {
308
cachedContent = dfos.getData();
310
return cachedContent;
313
byte[] fileData = new byte[(int) getSize()];
314
FileInputStream fis = null;
317
fis = new FileInputStream(dfos.getFile());
319
} catch (IOException e) {
325
} catch (IOException e) {
336
* Returns the contents of the file as a String, using the specified
337
* encoding. This method uses {@link #get()} to retrieve the
338
* contents of the file.
340
* @param charset The charset to use.
342
* @return The contents of the file, as a string.
344
* @throws UnsupportedEncodingException if the requested character
345
* encoding is not available.
347
public String getString(final String charset)
348
throws UnsupportedEncodingException {
349
return new String(get(), charset);
354
* Returns the contents of the file as a String, using the default
355
* character encoding. This method uses {@link #get()} to retrieve the
356
* contents of the file.
358
* @return The contents of the file, as a string.
360
* @todo Consider making this method throw UnsupportedEncodingException.
362
public String getString() {
363
byte[] rawdata = get();
364
String charset = getCharSet();
365
if (charset == null) {
366
charset = DEFAULT_CHARSET;
369
return new String(rawdata, charset);
370
} catch (UnsupportedEncodingException e) {
371
return new String(rawdata);
377
* A convenience method to write an uploaded item to disk. The client code
378
* is not concerned with whether or not the item is stored in memory, or on
379
* disk in a temporary location. They just want to write the uploaded item
382
* This implementation first attempts to rename the uploaded item to the
383
* specified destination file, if the item was originally written to disk.
384
* Otherwise, the data will be copied to the specified file.
386
* This method is only guaranteed to work <em>once</em>, the first time it
387
* is invoked for a particular item. This is because, in the event that the
388
* method renames a temporary file, that file will no longer be available
389
* to copy or rename again at a later time.
391
* @param file The <code>File</code> into which the uploaded item should
394
* @throws Exception if an error occurs.
396
public void write(File file) throws Exception {
398
FileOutputStream fout = null;
400
fout = new FileOutputStream(file);
408
File outputFile = getStoreLocation();
409
if (outputFile != null) {
410
// Save the length of the file
411
size = outputFile.length();
413
* The uploaded file is being stored on disk
414
* in a temporary location so move it to the
417
if (!outputFile.renameTo(file)) {
418
BufferedInputStream in = null;
419
BufferedOutputStream out = null;
421
in = new BufferedInputStream(
422
new FileInputStream(outputFile));
423
out = new BufferedOutputStream(
424
new FileOutputStream(file));
425
IOUtils.copy(in, out);
430
} catch (IOException e) {
437
} catch (IOException e) {
445
* For whatever reason we cannot write the
448
throw new FileUploadException(
449
"Cannot write uploaded file to disk!");
456
* Deletes the underlying storage for a file item, including deleting any
457
* associated temporary disk file. Although this storage will be deleted
458
* automatically when the <code>FileItem</code> instance is garbage
459
* collected, this method can be used to ensure that this is done at an
460
* earlier time, thus preserving system resources.
462
public void delete() {
463
cachedContent = null;
464
File outputFile = getStoreLocation();
465
if (outputFile != null && outputFile.exists()) {
472
* Returns the name of the field in the multipart form corresponding to
475
* @return The name of the form field.
477
* @see #setFieldName(java.lang.String)
480
public String getFieldName() {
486
* Sets the field name used to reference this file item.
488
* @param fieldName The name of the form field.
490
* @see #getFieldName()
493
public void setFieldName(String fieldName) {
494
this.fieldName = fieldName;
499
* Determines whether or not a <code>FileItem</code> instance represents
500
* a simple form field.
502
* @return <code>true</code> if the instance represents a simple form
503
* field; <code>false</code> if it represents an uploaded file.
505
* @see #setFormField(boolean)
508
public boolean isFormField() {
514
* Specifies whether or not a <code>FileItem</code> instance represents
515
* a simple form field.
517
* @param state <code>true</code> if the instance represents a simple form
518
* field; <code>false</code> if it represents an uploaded file.
520
* @see #isFormField()
523
public void setFormField(boolean state) {
529
* Returns an {@link java.io.OutputStream OutputStream} that can
530
* be used for storing the contents of the file.
532
* @return An {@link java.io.OutputStream OutputStream} that can be used
533
* for storing the contensts of the file.
535
* @throws IOException if an error occurs.
537
public OutputStream getOutputStream()
540
File outputFile = getTempFile();
541
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
547
// --------------------------------------------------------- Public methods
551
* Returns the {@link java.io.File} object for the <code>FileItem</code>'s
552
* data's temporary location on the disk. Note that for
553
* <code>FileItem</code>s that have their data stored in memory,
554
* this method will return <code>null</code>. When handling large
555
* files, you can use {@link java.io.File#renameTo(java.io.File)} to
556
* move the file to new location without copying the data, if the
557
* source and destination locations reside within the same logical
560
* @return The data file, or <code>null</code> if the data is stored in
563
public File getStoreLocation() {
564
return dfos.getFile();
568
// ------------------------------------------------------ Protected methods
572
* Removes the file contents from the temporary storage.
574
protected void finalize() {
575
File outputFile = dfos.getFile();
577
if (outputFile != null && outputFile.exists()) {
584
* Creates and returns a {@link java.io.File File} representing a uniquely
585
* named temporary file in the configured repository path. The lifetime of
586
* the file is tied to the lifetime of the <code>FileItem</code> instance;
587
* the file will be deleted when the instance is garbage collected.
589
* @return The {@link java.io.File File} to be used for temporary storage.
591
protected File getTempFile() {
592
File tempDir = repository;
593
if (tempDir == null) {
594
tempDir = new File(System.getProperty("java.io.tmpdir"));
597
String tempFileName = "upload_" + UID + "_" + getUniqueId() + ".tmp";
599
File f = new File(tempDir, tempFileName);
600
FileCleaner.track(f, this);
605
// -------------------------------------------------------- Private methods
609
* Returns an identifier that is unique within the class loader used to
610
* load this class, but does not have random-like apearance.
612
* @return A String with the non-random looking instance identifier.
614
private static String getUniqueId() {
615
final int limit = 100000000;
617
synchronized (DiskFileItem.class) {
620
String id = Integer.toString(current);
622
// If you manage to get more than 100 million of ids, you'll
623
// start getting ids longer than 8 characters.
624
if (current < limit) {
625
id = ("00000000" + id).substring(id.length());
634
* Returns a string representation of this object.
636
* @return a string representation of this object.
638
public String toString() {
639
return "name=" + this.getName()
641
+ String.valueOf(this.getStoreLocation())
645
+ "isFormField=" + isFormField()
647
+ this.getFieldName();
651
// -------------------------------------------------- Serialization methods
655
* Writes the state of this object during serialization.
657
* @param out The stream to which the state should be written.
659
* @throws IOException if an error occurs.
661
private void writeObject(ObjectOutputStream out) throws IOException {
663
if (dfos.isInMemory()) {
664
cachedContent = get();
666
cachedContent = null;
667
dfosFile = dfos.getFile();
671
out.defaultWriteObject();
675
* Reads the state of this object during deserialization.
677
* @param in The stream from which the state should be read.
679
* @throws IOException if an error occurs.
680
* @throws ClassNotFoundException if class cannot be found.
682
private void readObject(ObjectInputStream in)
683
throws IOException, ClassNotFoundException {
685
in.defaultReadObject();
687
OutputStream output = getOutputStream();
688
if (cachedContent != null) {
689
output.write(cachedContent);
691
FileInputStream input = new FileInputStream(dfosFile);
693
IOUtils.copy(input, output);
699
cachedContent = null;