All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.external.apache.commons.fileupload2.disk.DiskFileItem Maven / Gradle / Ivy

There is a newer version: 24.1.21
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.vaadin.external.apache.commons.fileupload2.disk;

import static java.lang.String.format;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import com.vaadin.external.apache.commons.fileupload2.FileItem;
import com.vaadin.external.apache.commons.fileupload2.FileItemHeaders;
import com.vaadin.external.apache.commons.fileupload2.FileUploadException;
import com.vaadin.external.apache.commons.fileupload2.ParameterParser;
import com.vaadin.external.apache.commons.fileupload2.util.Streams;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.DeferredFileOutputStream;

/**
 * 

* The default implementation of the * {@link com.vaadin.external.apache.commons.fileupload2.FileItem FileItem} * interface. * *

* After retrieving an instance of this class from a {@link DiskFileItemFactory} * instance (see * {@link com.vaadin.external.apache.commons.fileupload2.jaksrvlt.JakSrvltFileUpload * #parseRequest(jakarta.servlet.http.HttpServletRequest)}), you may either * request all contents of file at once using {@link #get()} or request an * {@link java.io.InputStream InputStream} with {@link #getInputStream()} and * process the file without attempting to load it into memory, which may come * handy with large files. * *

* Temporary files, which are created for file items, should be deleted later * on. The best way to do this is using a * {@link org.apache.commons.io.FileCleaningTracker}, which you can set on the * {@link DiskFileItemFactory}. However, if you do use such a tracker, then you * must consider the following: Temporary files are automatically deleted as * soon as they are no longer needed. (More precisely, when the corresponding * instance of {@link java.io.File} is garbage collected.) This is done by the * so-called reaper thread, which is started and stopped automatically by the * {@link org.apache.commons.io.FileCleaningTracker} when there are files to be * tracked. It might make sense to terminate that thread, for example, if your * web application ends. See the section on "Resource cleanup" in the users * guide of commons-fileupload. *

* * @since 1.1 */ public class DiskFileItem implements FileItem { // ----------------------------------------------------- Manifest constants /** * Default content charset to be used when no explicit charset parameter is * provided by the sender. Media subtypes of the "text" type are defined to * have a default charset value of "ISO-8859-1" when received via HTTP. */ public static final String DEFAULT_CHARSET = "ISO-8859-1"; // ----------------------------------------------------------- Data members /** * UID used in unique file name generation. */ private static final String UID = UUID.randomUUID().toString().replace('-', '_'); /** * Counter used in unique identifier generation. */ private static final AtomicInteger COUNTER = new AtomicInteger(0); /** * The name of the form field as provided by the browser. */ private String fieldName; /** * The content type passed by the browser, or {@code null} if not defined. */ private final String contentType; /** * Whether or not this item is a simple form field. */ private boolean isFormField; /** * The original file name in the user's file system. */ private final String fileName; /** * The size of the item, in bytes. This is used to cache the size when a * file item is moved from its original location. */ private long size = -1; /** * The threshold above which uploads will be stored on disk. */ private final int sizeThreshold; /** * The directory in which uploaded files will be stored, if stored on disk. */ private final File repository; /** * Cached contents of the file. */ private byte[] cachedContent; /** * Output stream for this item. */ private transient DeferredFileOutputStream dfos; /** * The temporary file to use. */ private transient File tempFile; /** * The file items headers. */ private FileItemHeaders headers; /** * Default content charset to be used when no explicit charset parameter is * provided by the sender. */ private String defaultCharset = DEFAULT_CHARSET; // ----------------------------------------------------------- Constructors /** * Constructs a new {@code DiskFileItem} instance. * * @param fieldName * The name of the form field. * @param contentType * The content type passed by the browser or {@code null} if not * specified. * @param isFormField * Whether or not this item is a plain form field, as opposed to * a file upload. * @param fileName * The original file name in the user's file system, or * {@code null} if not specified. * @param sizeThreshold * The threshold, in bytes, below which items will be retained in * memory and above which they will be stored as a file. * @param repository * The data repository, which is the directory in which files * will be created, should the item size exceed the threshold. */ public DiskFileItem(final String fieldName, final String contentType, final boolean isFormField, final String fileName, final int sizeThreshold, final File repository) { this.fieldName = fieldName; this.contentType = contentType; this.isFormField = isFormField; this.fileName = fileName; this.sizeThreshold = sizeThreshold; this.repository = repository; } // ------------------------------- Methods from javax.activation.DataSource /** * Returns an {@link java.io.InputStream InputStream} that can be used to * retrieve the contents of the file. * * @return An {@link java.io.InputStream InputStream} that can be used to * retrieve the contents of the file. * * @throws IOException * if an error occurs. */ @Override public InputStream getInputStream() throws IOException { if (!isInMemory()) { return Files.newInputStream(dfos.getFile().toPath()); } if (cachedContent == null) { cachedContent = dfos.getData(); } return new ByteArrayInputStream(cachedContent); } /** * Returns the content type passed by the agent or {@code null} if not * defined. * * @return The content type passed by the agent or {@code null} if not * defined. */ @Override public String getContentType() { return contentType; } /** * Returns the content charset passed by the agent or {@code null} if not * defined. * * @return The content charset passed by the agent or {@code null} if not * defined. */ public String getCharSet() { final ParameterParser parser = new ParameterParser(); parser.setLowerCaseNames(true); // Parameter parser can handle null input final Map params = parser.parse(getContentType(), ';'); return params.get("charset"); } /** * Returns the original file name in the client's file system. * * @return The original file name in the client's file system. * @throws com.vaadin.external.apache.commons.fileupload2.InvalidFileNameException * The file name contains a NUL character, which might be an * indicator of a security attack. If you intend to use the file * name anyways, catch the exception and use * {@link com.vaadin.external.apache.commons.fileupload2.InvalidFileNameException#getName()}. */ @Override public String getName() { return Streams.checkFileName(fileName); } // ------------------------------------------------------- FileItem methods /** * Provides a hint as to whether or not the file contents will be read from * memory. * * @return {@code true} if the file contents will be read from memory; * {@code false} otherwise. */ @Override public boolean isInMemory() { if (cachedContent != null) { return true; } return dfos.isInMemory(); } /** * Returns the size of the file. * * @return The size of the file, in bytes. */ @Override public long getSize() { if (size >= 0) { return size; } if (cachedContent != null) { return cachedContent.length; } if (dfos.isInMemory()) { return dfos.getData().length; } return dfos.getFile().length(); } /** * Returns the contents of the file as an array of bytes. If the contents of * the file were not yet cached in memory, they will be loaded from the disk * storage and cached. * * @return The contents of the file as an array of bytes or {@code null} if * the data cannot be read * * @throws UncheckedIOException * if an I/O error occurs */ @Override public byte[] get() throws UncheckedIOException { if (isInMemory()) { if (cachedContent == null && dfos != null) { cachedContent = dfos.getData(); } return cachedContent != null ? cachedContent.clone() : new byte[0]; } final byte[] fileData = new byte[(int) getSize()]; try (InputStream fis = Files.newInputStream(dfos.getFile().toPath())) { IOUtils.readFully(fis, fileData); } catch (final IOException e) { throw new UncheckedIOException(e); } return fileData; } /** * Returns the contents of the file as a String, using the specified * encoding. This method uses {@link #get()} to retrieve the contents of the * file. * * @param charset * The charset to use. * * @return The contents of the file, as a string. * * @throws UnsupportedEncodingException * if the requested character encoding is not available. */ @Override public String getString(final String charset) throws UnsupportedEncodingException, IOException { return new String(get(), charset); } /** * Returns the contents of the file as a String, using the default character * encoding. This method uses {@link #get()} to retrieve the contents of the * file. * * TODO Consider making this method throw * UnsupportedEncodingException. * * @return The contents of the file, as a string. */ @Override public String getString() { try { final byte[] rawData = get(); String charset = getCharSet(); if (charset == null) { charset = defaultCharset; } return new String(rawData, charset); } catch (final IOException e) { return ""; } } /** * A convenience method to write an uploaded item to disk. The client code * is not concerned with whether or not the item is stored in memory, or on * disk in a temporary location. They just want to write the uploaded item * to a file. *

* This implementation first attempts to rename the uploaded item to the * specified destination file, if the item was originally written to disk. * Otherwise, the data will be copied to the specified file. *

* This method is only guaranteed to work once, the first time it * is invoked for a particular item. This is because, in the event that the * method renames a temporary file, that file will no longer be available to * copy or rename again at a later time. * * @param file * The {@code File} into which the uploaded item should be * stored. * * @throws Exception * if an error occurs. */ @Override public void write(final File file) throws Exception { if (isInMemory()) { try (OutputStream fout = Files.newOutputStream(file.toPath())) { fout.write(get()); } catch (final IOException e) { throw new IOException("Unexpected output data"); } } else { final File outputFile = getStoreLocation(); if (outputFile == null) { /* * For whatever reason we cannot write the file to disk. */ throw new FileUploadException( "Cannot write uploaded file to disk!"); } // Save the length of the file size = outputFile.length(); /* * The uploaded file is being stored on disk in a temporary location * so move it to the desired file. */ if (file.exists() && !file.delete()) { throw new FileUploadException( "Cannot write uploaded file to disk!"); } FileUtils.moveFile(outputFile, file); } } /** * Deletes the underlying storage for a file item, including deleting any * associated temporary disk file. This method can be used to ensure that * this is done at an earlier time, thus preserving system resources. */ @Override public void delete() { cachedContent = null; final File outputFile = getStoreLocation(); if (outputFile != null && !isInMemory() && outputFile.exists()) { if (!outputFile.delete()) { final String desc = "Cannot delete " + outputFile.toString(); throw new UncheckedIOException(desc, new IOException(desc)); } } } /** * Returns the name of the field in the multipart form corresponding to this * file item. * * @return The name of the form field. * * @see #setFieldName(String) * */ @Override public String getFieldName() { return fieldName; } /** * Sets the field name used to reference this file item. * * @param fieldName * The name of the form field. * * @see #getFieldName() * */ @Override public void setFieldName(final String fieldName) { this.fieldName = fieldName; } /** * Determines whether or not a {@code FileItem} instance represents a simple * form field. * * @return {@code true} if the instance represents a simple form field; * {@code false} if it represents an uploaded file. * * @see #setFormField(boolean) * */ @Override public boolean isFormField() { return isFormField; } /** * Specifies whether or not a {@code FileItem} instance represents a simple * form field. * * @param state * {@code true} if the instance represents a simple form field; * {@code false} if it represents an uploaded file. * * @see #isFormField() * */ @Override public void setFormField(final boolean state) { isFormField = state; } /** * Returns an {@link java.io.OutputStream OutputStream} that can be used for * storing the contents of the file. * * @return An {@link java.io.OutputStream OutputStream} that can be used for * storing the contents of the file. * */ @Override public OutputStream getOutputStream() { if (dfos == null) { final File outputFile = getTempFile(); dfos = new DeferredFileOutputStream(sizeThreshold, outputFile); } return dfos; } // --------------------------------------------------------- Public methods /** * Returns the {@link java.io.File} object for the {@code FileItem}'s data's * temporary location on the disk. Note that for {@code FileItem}s that have * their data stored in memory, this method will return {@code null}. When * handling large files, you can use * {@link java.io.File#renameTo(java.io.File)} to move the file to new * location without copying the data, if the source and destination * locations reside within the same logical volume. * * @return The data file, or {@code null} if the data is stored in memory. */ public File getStoreLocation() { if (dfos == null) { return null; } if (isInMemory()) { return null; } return dfos.getFile(); } // ------------------------------------------------------ Protected methods /** * Creates and returns a {@link java.io.File File} representing a uniquely * named temporary file in the configured repository path. The lifetime of * the file is tied to the lifetime of the {@code FileItem} instance; the * file will be deleted when the instance is garbage collected. *

* Note: Subclasses that override this method must ensure that they * return the same File each time. * * @return The {@link java.io.File File} to be used for temporary storage. */ protected File getTempFile() { if (tempFile == null) { File tempDir = repository; if (tempDir == null) { tempDir = new File(System.getProperty("java.io.tmpdir")); } final String tempFileName = format("upload_%s_%s.tmp", UID, getUniqueId()); tempFile = new File(tempDir, tempFileName); } return tempFile; } // -------------------------------------------------------- Private methods /** * Returns an identifier that is unique within the class loader used to load * this class, but does not have random-like appearance. * * @return A String with the non-random looking instance identifier. */ private static String getUniqueId() { final int limit = 100000000; final int current = COUNTER.getAndIncrement(); String id = Integer.toString(current); // If you manage to get more than 100 million of ids, you'll // start getting ids longer than 8 characters. if (current < limit) { id = ("00000000" + id).substring(id.length()); } return id; } /** * Returns a string representation of this object. * * @return a string representation of this object. */ @Override public String toString() { return format( "name=%s, StoreLocation=%s, size=%s bytes, isFormField=%s, FieldName=%s", getName(), getStoreLocation(), getSize(), isFormField(), getFieldName()); } /** * Returns the file item headers. * * @return The file items headers. */ @Override public FileItemHeaders getHeaders() { return headers; } /** * Sets the file item headers. * * @param pHeaders * The file items headers. */ @Override public void setHeaders(final FileItemHeaders pHeaders) { headers = pHeaders; } /** * Returns the default charset for use when no explicit charset parameter is * provided by the sender. * * @return the default charset */ public String getDefaultCharset() { return defaultCharset; } /** * Sets the default charset for use when no explicit charset parameter is * provided by the sender. * * @param charset * the default charset */ public void setDefaultCharset(final String charset) { defaultCharset = charset; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy