~ubuntu-branches/ubuntu/lucid/libcommons-fileupload-java/lucid

« back to all changes in this revision

Viewing changes to src/java/org/apache/commons/fileupload/disk/DiskFileItem.java

  • Committer: Bazaar Package Importer
  • Author(s): Michael Koch
  • Date: 2007-09-01 22:19:03 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20070901221903-sksemtr2w1avrocv
Tags: 1.2-2
Added Depends on libcommons-io-java. Closes: #439860.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
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
 
8
 *
 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
 
10
 *
 
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.
 
16
 */
 
17
package org.apache.commons.fileupload.disk;
 
18
 
 
19
import java.io.BufferedInputStream;
 
20
import java.io.BufferedOutputStream;
 
21
import java.io.ByteArrayInputStream;
 
22
import java.io.File;
 
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;
 
31
import java.util.Map;
 
32
import org.apache.commons.io.IOUtils;
 
33
import org.apache.commons.io.FileCleaner;
 
34
import org.apache.commons.io.output.DeferredFileOutputStream;
 
35
 
 
36
import org.apache.commons.fileupload.FileItem;
 
37
import org.apache.commons.fileupload.FileUploadException;
 
38
import org.apache.commons.fileupload.ParameterParser;
 
39
 
 
40
 
 
41
/**
 
42
 * <p> The default implementation of the
 
43
 * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
 
44
 *
 
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.
 
53
 *
 
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>
 
63
 *
 
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
 
70
 *
 
71
 * @since FileUpload 1.1
 
72
 *
 
73
 * @version $Id: DiskFileItem.java 482993 2006-12-06 09:42:01Z jochen $
 
74
 */
 
75
public class DiskFileItem
 
76
    implements FileItem {
 
77
 
 
78
    // ----------------------------------------------------- Manifest constants
 
79
 
 
80
 
 
81
    /**
 
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.
 
86
     */
 
87
    public static final String DEFAULT_CHARSET = "ISO-8859-1";
 
88
 
 
89
 
 
90
    // ----------------------------------------------------------- Data members
 
91
 
 
92
 
 
93
    /**
 
94
     * UID used in unique file name generation.
 
95
     */
 
96
    private static final String UID =
 
97
            new java.rmi.server.UID().toString()
 
98
                .replace(':', '_').replace('-', '_');
 
99
 
 
100
    /**
 
101
     * Counter used in unique identifier generation.
 
102
     */
 
103
    private static int counter = 0;
 
104
 
 
105
 
 
106
    /**
 
107
     * The name of the form field as provided by the browser.
 
108
     */
 
109
    private String fieldName;
 
110
 
 
111
 
 
112
    /**
 
113
     * The content type passed by the browser, or <code>null</code> if
 
114
     * not defined.
 
115
     */
 
116
    private String contentType;
 
117
 
 
118
 
 
119
    /**
 
120
     * Whether or not this item is a simple form field.
 
121
     */
 
122
    private boolean isFormField;
 
123
 
 
124
 
 
125
    /**
 
126
     * The original filename in the user's filesystem.
 
127
     */
 
128
    private String fileName;
 
129
 
 
130
 
 
131
    /**
 
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.
 
134
     */
 
135
    private long size = -1;
 
136
 
 
137
 
 
138
    /**
 
139
     * The threshold above which uploads will be stored on disk.
 
140
     */
 
141
    private int sizeThreshold;
 
142
 
 
143
 
 
144
    /**
 
145
     * The directory in which uploaded files will be stored, if stored on disk.
 
146
     */
 
147
    private File repository;
 
148
 
 
149
 
 
150
    /**
 
151
     * Cached contents of the file.
 
152
     */
 
153
    private byte[] cachedContent;
 
154
 
 
155
 
 
156
    /**
 
157
     * Output stream for this item.
 
158
     */
 
159
    private transient DeferredFileOutputStream dfos;
 
160
 
 
161
    /**
 
162
     * File to allow for serialization of the content of this item.
 
163
     */
 
164
    private File dfosFile;
 
165
 
 
166
 
 
167
    // ----------------------------------------------------------- Constructors
 
168
 
 
169
 
 
170
    /**
 
171
     * Constructs a new <code>DiskFileItem</code> instance.
 
172
     *
 
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
 
182
     *                      stored as a file.
 
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.
 
186
     */
 
187
    public DiskFileItem(String fieldName, String contentType,
 
188
            boolean isFormField, String fileName, int sizeThreshold,
 
189
            File repository) {
 
190
        this.fieldName = fieldName;
 
191
        this.contentType = contentType;
 
192
        this.isFormField = isFormField;
 
193
        this.fileName = fileName;
 
194
        this.sizeThreshold = sizeThreshold;
 
195
        this.repository = repository;
 
196
    }
 
197
 
 
198
 
 
199
    // ------------------------------- Methods from javax.activation.DataSource
 
200
 
 
201
 
 
202
    /**
 
203
     * Returns an {@link java.io.InputStream InputStream} that can be
 
204
     * used to retrieve the contents of the file.
 
205
     *
 
206
     * @return An {@link java.io.InputStream InputStream} that can be
 
207
     *         used to retrieve the contents of the file.
 
208
     *
 
209
     * @throws IOException if an error occurs.
 
210
     */
 
211
    public InputStream getInputStream()
 
212
        throws IOException {
 
213
        if (!isInMemory()) {
 
214
            return new FileInputStream(dfos.getFile());
 
215
        }
 
216
 
 
217
        if (cachedContent == null) {
 
218
            cachedContent = dfos.getData();
 
219
        }
 
220
        return new ByteArrayInputStream(cachedContent);
 
221
    }
 
222
 
 
223
 
 
224
    /**
 
225
     * Returns the content type passed by the agent or <code>null</code> if
 
226
     * not defined.
 
227
     *
 
228
     * @return The content type passed by the agent or <code>null</code> if
 
229
     *         not defined.
 
230
     */
 
231
    public String getContentType() {
 
232
        return contentType;
 
233
    }
 
234
 
 
235
 
 
236
    /**
 
237
     * Returns the content charset passed by the agent or <code>null</code> if
 
238
     * not defined.
 
239
     *
 
240
     * @return The content charset passed by the agent or <code>null</code> if
 
241
     *         not defined.
 
242
     */
 
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");
 
249
    }
 
250
 
 
251
 
 
252
    /**
 
253
     * Returns the original filename in the client's filesystem.
 
254
     *
 
255
     * @return The original filename in the client's filesystem.
 
256
     */
 
257
    public String getName() {
 
258
        return fileName;
 
259
    }
 
260
 
 
261
 
 
262
    // ------------------------------------------------------- FileItem methods
 
263
 
 
264
 
 
265
    /**
 
266
     * Provides a hint as to whether or not the file contents will be read
 
267
     * from memory.
 
268
     *
 
269
     * @return <code>true</code> if the file contents will be read
 
270
     *         from memory; <code>false</code> otherwise.
 
271
     */
 
272
    public boolean isInMemory() {
 
273
        if (cachedContent != null) {
 
274
            return true;
 
275
        }
 
276
        return dfos.isInMemory();
 
277
    }
 
278
 
 
279
 
 
280
    /**
 
281
     * Returns the size of the file.
 
282
     *
 
283
     * @return The size of the file, in bytes.
 
284
     */
 
285
    public long getSize() {
 
286
        if (size >= 0) {
 
287
            return size;
 
288
        } else if (cachedContent != null) {
 
289
            return cachedContent.length;
 
290
        } else if (dfos.isInMemory()) {
 
291
            return dfos.getData().length;
 
292
        } else {
 
293
            return dfos.getFile().length();
 
294
        }
 
295
    }
 
296
 
 
297
 
 
298
    /**
 
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.
 
302
     *
 
303
     * @return The contents of the file as an array of bytes.
 
304
     */
 
305
    public byte[] get() {
 
306
        if (isInMemory()) {
 
307
            if (cachedContent == null) {
 
308
                cachedContent = dfos.getData();
 
309
            }
 
310
            return cachedContent;
 
311
        }
 
312
 
 
313
        byte[] fileData = new byte[(int) getSize()];
 
314
        FileInputStream fis = null;
 
315
 
 
316
        try {
 
317
            fis = new FileInputStream(dfos.getFile());
 
318
            fis.read(fileData);
 
319
        } catch (IOException e) {
 
320
            fileData = null;
 
321
        } finally {
 
322
            if (fis != null) {
 
323
                try {
 
324
                    fis.close();
 
325
                } catch (IOException e) {
 
326
                    // ignore
 
327
                }
 
328
            }
 
329
        }
 
330
 
 
331
        return fileData;
 
332
    }
 
333
 
 
334
 
 
335
    /**
 
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.
 
339
     *
 
340
     * @param charset The charset to use.
 
341
     *
 
342
     * @return The contents of the file, as a string.
 
343
     *
 
344
     * @throws UnsupportedEncodingException if the requested character
 
345
     *                                      encoding is not available.
 
346
     */
 
347
    public String getString(final String charset)
 
348
        throws UnsupportedEncodingException {
 
349
        return new String(get(), charset);
 
350
    }
 
351
 
 
352
 
 
353
    /**
 
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.
 
357
     *
 
358
     * @return The contents of the file, as a string.
 
359
     *
 
360
     * @todo Consider making this method throw UnsupportedEncodingException.
 
361
     */
 
362
    public String getString() {
 
363
        byte[] rawdata = get();
 
364
        String charset = getCharSet();
 
365
        if (charset == null) {
 
366
            charset = DEFAULT_CHARSET;
 
367
        }
 
368
        try {
 
369
            return new String(rawdata, charset);
 
370
        } catch (UnsupportedEncodingException e) {
 
371
            return new String(rawdata);
 
372
        }
 
373
    }
 
374
 
 
375
 
 
376
    /**
 
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
 
380
     * to a file.
 
381
     * <p>
 
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.
 
385
     * <p>
 
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.
 
390
     *
 
391
     * @param file The <code>File</code> into which the uploaded item should
 
392
     *             be stored.
 
393
     *
 
394
     * @throws Exception if an error occurs.
 
395
     */
 
396
    public void write(File file) throws Exception {
 
397
        if (isInMemory()) {
 
398
            FileOutputStream fout = null;
 
399
            try {
 
400
                fout = new FileOutputStream(file);
 
401
                fout.write(get());
 
402
            } finally {
 
403
                if (fout != null) {
 
404
                    fout.close();
 
405
                }
 
406
            }
 
407
        } else {
 
408
            File outputFile = getStoreLocation();
 
409
            if (outputFile != null) {
 
410
                // Save the length of the file
 
411
                size = outputFile.length();
 
412
                /*
 
413
                 * The uploaded file is being stored on disk
 
414
                 * in a temporary location so move it to the
 
415
                 * desired file.
 
416
                 */
 
417
                if (!outputFile.renameTo(file)) {
 
418
                    BufferedInputStream in = null;
 
419
                    BufferedOutputStream out = null;
 
420
                    try {
 
421
                        in = new BufferedInputStream(
 
422
                            new FileInputStream(outputFile));
 
423
                        out = new BufferedOutputStream(
 
424
                                new FileOutputStream(file));
 
425
                        IOUtils.copy(in, out);
 
426
                    } finally {
 
427
                        if (in != null) {
 
428
                            try {
 
429
                                in.close();
 
430
                            } catch (IOException e) {
 
431
                                // ignore
 
432
                            }
 
433
                        }
 
434
                        if (out != null) {
 
435
                            try {
 
436
                                out.close();
 
437
                            } catch (IOException e) {
 
438
                                // ignore
 
439
                            }
 
440
                        }
 
441
                    }
 
442
                }
 
443
            } else {
 
444
                /*
 
445
                 * For whatever reason we cannot write the
 
446
                 * file to disk.
 
447
                 */
 
448
                throw new FileUploadException(
 
449
                    "Cannot write uploaded file to disk!");
 
450
            }
 
451
        }
 
452
    }
 
453
 
 
454
 
 
455
    /**
 
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.
 
461
     */
 
462
    public void delete() {
 
463
        cachedContent = null;
 
464
        File outputFile = getStoreLocation();
 
465
        if (outputFile != null && outputFile.exists()) {
 
466
            outputFile.delete();
 
467
        }
 
468
    }
 
469
 
 
470
 
 
471
    /**
 
472
     * Returns the name of the field in the multipart form corresponding to
 
473
     * this file item.
 
474
     *
 
475
     * @return The name of the form field.
 
476
     *
 
477
     * @see #setFieldName(java.lang.String)
 
478
     *
 
479
     */
 
480
    public String getFieldName() {
 
481
        return fieldName;
 
482
    }
 
483
 
 
484
 
 
485
    /**
 
486
     * Sets the field name used to reference this file item.
 
487
     *
 
488
     * @param fieldName The name of the form field.
 
489
     *
 
490
     * @see #getFieldName()
 
491
     *
 
492
     */
 
493
    public void setFieldName(String fieldName) {
 
494
        this.fieldName = fieldName;
 
495
    }
 
496
 
 
497
 
 
498
    /**
 
499
     * Determines whether or not a <code>FileItem</code> instance represents
 
500
     * a simple form field.
 
501
     *
 
502
     * @return <code>true</code> if the instance represents a simple form
 
503
     *         field; <code>false</code> if it represents an uploaded file.
 
504
     *
 
505
     * @see #setFormField(boolean)
 
506
     *
 
507
     */
 
508
    public boolean isFormField() {
 
509
        return isFormField;
 
510
    }
 
511
 
 
512
 
 
513
    /**
 
514
     * Specifies whether or not a <code>FileItem</code> instance represents
 
515
     * a simple form field.
 
516
     *
 
517
     * @param state <code>true</code> if the instance represents a simple form
 
518
     *              field; <code>false</code> if it represents an uploaded file.
 
519
     *
 
520
     * @see #isFormField()
 
521
     *
 
522
     */
 
523
    public void setFormField(boolean state) {
 
524
        isFormField = state;
 
525
    }
 
526
 
 
527
 
 
528
    /**
 
529
     * Returns an {@link java.io.OutputStream OutputStream} that can
 
530
     * be used for storing the contents of the file.
 
531
     *
 
532
     * @return An {@link java.io.OutputStream OutputStream} that can be used
 
533
     *         for storing the contensts of the file.
 
534
     *
 
535
     * @throws IOException if an error occurs.
 
536
     */
 
537
    public OutputStream getOutputStream()
 
538
        throws IOException {
 
539
        if (dfos == null) {
 
540
            File outputFile = getTempFile();
 
541
            dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
 
542
        }
 
543
        return dfos;
 
544
    }
 
545
 
 
546
 
 
547
    // --------------------------------------------------------- Public methods
 
548
 
 
549
 
 
550
    /**
 
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
 
558
     * volume.
 
559
     *
 
560
     * @return The data file, or <code>null</code> if the data is stored in
 
561
     *         memory.
 
562
     */
 
563
    public File getStoreLocation() {
 
564
        return dfos.getFile();
 
565
    }
 
566
 
 
567
 
 
568
    // ------------------------------------------------------ Protected methods
 
569
 
 
570
 
 
571
    /**
 
572
     * Removes the file contents from the temporary storage.
 
573
     */
 
574
    protected void finalize() {
 
575
        File outputFile = dfos.getFile();
 
576
 
 
577
        if (outputFile != null && outputFile.exists()) {
 
578
            outputFile.delete();
 
579
        }
 
580
    }
 
581
 
 
582
 
 
583
    /**
 
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.
 
588
     *
 
589
     * @return The {@link java.io.File File} to be used for temporary storage.
 
590
     */
 
591
    protected File getTempFile() {
 
592
        File tempDir = repository;
 
593
        if (tempDir == null) {
 
594
            tempDir = new File(System.getProperty("java.io.tmpdir"));
 
595
        }
 
596
 
 
597
        String tempFileName = "upload_" + UID + "_" + getUniqueId() + ".tmp";
 
598
 
 
599
        File f = new File(tempDir, tempFileName);
 
600
        FileCleaner.track(f, this);
 
601
        return f;
 
602
    }
 
603
 
 
604
 
 
605
    // -------------------------------------------------------- Private methods
 
606
 
 
607
 
 
608
    /**
 
609
     * Returns an identifier that is unique within the class loader used to
 
610
     * load this class, but does not have random-like apearance.
 
611
     *
 
612
     * @return A String with the non-random looking instance identifier.
 
613
     */
 
614
    private static String getUniqueId() {
 
615
        final int limit = 100000000;
 
616
        int current;
 
617
        synchronized (DiskFileItem.class) {
 
618
            current = counter++;
 
619
        }
 
620
        String id = Integer.toString(current);
 
621
 
 
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());
 
626
        }
 
627
        return id;
 
628
    }
 
629
 
 
630
 
 
631
 
 
632
 
 
633
    /**
 
634
     * Returns a string representation of this object.
 
635
     *
 
636
     * @return a string representation of this object.
 
637
     */
 
638
    public String toString() {
 
639
        return "name=" + this.getName()
 
640
            + ", StoreLocation="
 
641
            + String.valueOf(this.getStoreLocation())
 
642
            + ", size="
 
643
            + this.getSize()
 
644
            + "bytes, "
 
645
            + "isFormField=" + isFormField()
 
646
            + ", FieldName="
 
647
            + this.getFieldName();
 
648
    }
 
649
 
 
650
 
 
651
    // -------------------------------------------------- Serialization methods
 
652
 
 
653
 
 
654
    /**
 
655
     * Writes the state of this object during serialization.
 
656
     *
 
657
     * @param out The stream to which the state should be written.
 
658
     *
 
659
     * @throws IOException if an error occurs.
 
660
     */
 
661
    private void writeObject(ObjectOutputStream out) throws IOException {
 
662
        // Read the data
 
663
        if (dfos.isInMemory()) {
 
664
            cachedContent = get();
 
665
        } else {
 
666
            cachedContent = null;
 
667
            dfosFile = dfos.getFile();
 
668
        }
 
669
 
 
670
        // write out values
 
671
        out.defaultWriteObject();
 
672
    }
 
673
 
 
674
    /**
 
675
     * Reads the state of this object during deserialization.
 
676
     *
 
677
     * @param in The stream from which the state should be read.
 
678
     *
 
679
     * @throws IOException if an error occurs.
 
680
     * @throws ClassNotFoundException if class cannot be found.
 
681
     */
 
682
    private void readObject(ObjectInputStream in)
 
683
            throws IOException, ClassNotFoundException {
 
684
        // read values
 
685
        in.defaultReadObject();
 
686
 
 
687
        OutputStream output = getOutputStream();
 
688
        if (cachedContent != null) {
 
689
            output.write(cachedContent);
 
690
        } else {
 
691
            FileInputStream input = new FileInputStream(dfosFile);
 
692
 
 
693
            IOUtils.copy(input, output);
 
694
            dfosFile.delete();
 
695
            dfosFile = null;
 
696
        }
 
697
        output.close();
 
698
 
 
699
        cachedContent = null;
 
700
    }
 
701
 
 
702
}