/******************************************************************************* * Copyright (c) 2002-2006 Innoopract Informationssysteme GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Innoopract Informationssysteme GmbH - initial API and implementation ******************************************************************************/ package com.w4t.engine.requests; import java.io.*; import org.apache.commons.fileupload.DeferredFileOutputStream; import org.apache.commons.fileupload.FileItem; /** * <p> * The FileItem implementation of W4Toolkit provides some optimizations * regarding the memory footprint of formfields items. * </p> */ class UploadRequestFileItem implements FileItem { private static final long serialVersionUID = 1L; private static final int DEFAULT_OUTPUTSTREAM_SIZE = 32; private static int counter = 0; private String fieldName; private String contentType; private boolean isFormField; private String fileName; private int sizeThreshold; private File repository; private byte[] cachedContent; private DeferredFileOutputStream dfos; /** * <p>creates a new <code>UploadRequestFileItem</code> instance.</p> * * @param fieldName The name of the form field. * @param contentType The content type passed by the browser or * <code>null</code> 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 filename in the user's filesystem, or * <code>null</code> 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. */ UploadRequestFileItem( 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; } /** * <p>returns an {@link java.io.InputStream InputStream}that can be used to * retrieve the contents of the file.</p> * * @return An {@link java.io.InputStream InputStream}that can be used to * retrieve the contents of the file. * @exception IOException if an error occurs. */ public InputStream getInputStream() throws IOException { InputStream result = null; if( !dfos.isInMemory() ) { result = new FileInputStream( dfos.getFile() ); } else { if( cachedContent == null ) { cachedContent = dfos.getData(); } result = new ByteArrayInputStream( cachedContent ); } return result; } /** * <p>returns the content type passed by the browser or <code>null</code> if * not defined.</p> * * @return The content type passed by the browser or <code>null</code> if * not defined. */ public String getContentType() { return contentType; } /** * <p>returns the original filename in the client's filesystem.</p> * * @return The original filename in the client's filesystem. */ public String getName() { return fileName; } /** * <p>provides a hint as to whether or not the file contents will be read from * memory.</p> * * @return <code>true</code> if the file contents will be read from memory; * <code>false</code> otherwise. */ public boolean isInMemory() { return dfos.isInMemory(); } /** * <p>returns the size of the file.</p> * * @return The size of the file, in bytes. */ public long getSize() { long result = 0; if( cachedContent != null ) { result = cachedContent.length; } else if( dfos.isInMemory() ) { result = dfos.getData().length; } else { result = dfos.getFile().length(); } return result; } /** * <p>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.</p> * * @return The contents of the file as an array of bytes. */ public byte[] get() { byte[] result = null; if( dfos.isInMemory() ) { if( cachedContent == null ) { cachedContent = dfos.getData(); } result = cachedContent; } else { byte[] fileData = new byte[ ( int )getSize() ]; FileInputStream fis = null; try { fis = new FileInputStream( dfos.getFile() ); fis.read( fileData ); } catch( IOException e ) { fileData = null; } finally { if( fis != null ) { try { fis.close(); } catch( IOException e ) { // ignore } } } result = fileData; } return result; } /** * <p>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.</p> * * @param encoding The character encoding to use. * @return The contents of the file, as a string. * @exception UnsupportedEncodingException if the requested character encoding * is not available. */ public String getString( final String encoding ) throws UnsupportedEncodingException { return new String( get(), encoding ); } /** * <p>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.</p> * * @return The contents of the file, as a string. */ public String getString() { return new String( get() ); } /** * <p>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.</p> * * <p>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.</p> * * <p>This method is only guaranteed to work <em>once</em>, 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.</p> * * @param file The <code>File</code> into which the uploaded item should be * stored. * @exception Exception if an error occurs. */ public void write( final File file ) throws IOException { if( isInMemory() ) { FileOutputStream fout = null; try { fout = new FileOutputStream( file ); fout.write( get() ); } finally { if( fout != null ) { fout.close(); } } } else { File outputFile = getStoreLocation(); if( outputFile != null ) { /* * The uploaded file is being stored on disk in a temporary location so * move it to the desired file. */ if( !outputFile.renameTo( file ) ) { BufferedInputStream in = null; BufferedOutputStream out = null; try { in = new BufferedInputStream( new FileInputStream( outputFile ) ); out = new BufferedOutputStream( new FileOutputStream( file ) ); byte[] bytes = new byte[ 2048 ]; int s = 0; while( ( s = in.read( bytes ) ) != -1 ) { out.write( bytes, 0, s ); } } finally { try { if( in != null ) { in.close(); } } catch( IOException e ) { // ignore } try { if( out != null ) { out.close(); } } catch( IOException e ) { // ignore } } } } else { /* * For whatever reason we cannot write the file to disk. */ throw new IOException( "Cannot write uploaded file to disk!" ); } } } /** * <p>deletes the underlying storage for a file item, including deleting any * associated temporary disk file. Although this storage will be deleted * automatically when the <code>FileItem</code> instance is garbage * collected, this method can be used to ensure that this is done at an * earlier time, thus preserving system resources.</p> */ public void delete() { cachedContent = null; File outputFile = getStoreLocation(); if( outputFile != null && outputFile.exists() ) { outputFile.delete(); } } /** * <p>returns the name of the field in the multipart form corresponding to * this file item.</p> * * @return The name of the form field. * @see #setFieldName(java.lang.String) */ public String getFieldName() { return fieldName; } /** * <p>sets the field name used to reference this file item.</p> * * @param fieldName The name of the form field. * @see #getFieldName() */ public void setFieldName( final String fieldName ) { this.fieldName = fieldName; } /** * <p>determines whether or not a <code>FileItem</code> instance represents a * simple form field.</p> * * @return <code>true</code> if the instance represents a simple form field; * <code>false</code> if it represents an uploaded file. * @see #setFormField(boolean) */ public boolean isFormField() { return isFormField; } /** * <p>specifies whether or not a <code>FileItem</code> instance represents a * simple form field.</p> * * @param isFormField <code>true</code> if the instance represents a simple * form field; <code>false</code> if it represents an uploaded file. * @see #isFormField() */ public void setFormField( final boolean isFormField ) { this.isFormField = isFormField; } /** * <p>returns an {@link java.io.OutputStream OutputStream}that can be used for * storing the contents of the file.</p> * * @return An {@link java.io.OutputStream OutputStream}that can be used for * storing the contensts of the file. * @exception IOException if an error occurs. */ public OutputStream getOutputStream() throws IOException { if( dfos == null ) { File outputFile = getTempFile(); if( isFormField() ) { dfos = new DeferredFileOutputStream( DEFAULT_OUTPUTSTREAM_SIZE, outputFile ); } else { dfos = new DeferredFileOutputStream( sizeThreshold, outputFile ); } } return dfos; } /** * <p>returns the {@link java.io.File}object for the <code>FileItem</code>'s * data's temporary location on the disk. Note that for <code>FileItem</code> * s that have their data stored in memory, this method will return * <code>null</code>. 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.</p> * * @return The data file, or <code>null</code> if the data is stored in * memory. */ public File getStoreLocation() { return dfos.getFile(); } /** * <p>removes the file contents from the temporary storage.</p> */ protected void finalize() { File outputFile = dfos.getFile(); if( outputFile != null && outputFile.exists() ) { outputFile.delete(); } } /** * <p>creates and returns a {@link java.io.File File}representing a uniquely * named temporary file in the configured repository path.</p> * * @return The {@link java.io.File File}to be used for temporary storage. */ protected File getTempFile() { File tempDir = repository; if( tempDir == null ) { tempDir = new File( System.getProperty( "java.io.tmpdir" ) ); } String fileName = "upload_" + getUniqueId() + ".tmp"; File result = new File( tempDir, fileName ); result.deleteOnExit(); return result; } /** * <p>returns an identifier that is unique within the class loader used to * load this class, but does not have random-like apearance.</p> * * @return A String with the non-random looking instance identifier. */ private static String getUniqueId() { int current; synchronized( UploadRequestFileItem.class ) { current = counter++; } String result = 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 < 100000000 ) { result = ( "00000000" + result ).substring( result.length() ); } return result; } }