/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project 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 io.netty.handler.codec.http.multipart;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.util.internal.EmptyArrays;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

import static io.netty.buffer.Unpooled.*;

Abstract Disk HttpData implementation
/** * Abstract Disk HttpData implementation */
public abstract class AbstractDiskHttpData extends AbstractHttpData { private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractDiskHttpData.class); private File file; private boolean isRenamed; private FileChannel fileChannel; protected AbstractDiskHttpData(String name, Charset charset, long size) { super(name, charset, size); }
Returns:the real DiskFilename (basename)
/** * * @return the real DiskFilename (basename) */
protected abstract String getDiskFilename();
Returns:the default prefix
/** * * @return the default prefix */
protected abstract String getPrefix();
Returns:the default base Directory
/** * * @return the default base Directory */
protected abstract String getBaseDirectory();
Returns:the default postfix
/** * * @return the default postfix */
protected abstract String getPostfix();
Returns:True if the file should be deleted on Exit by default
/** * * @return True if the file should be deleted on Exit by default */
protected abstract boolean deleteOnExit();
Returns:a new Temp File from getDiskFilename(), default prefix, postfix and baseDirectory
/** * @return a new Temp File from getDiskFilename(), default prefix, postfix and baseDirectory */
private File tempFile() throws IOException { String newpostfix; String diskFilename = getDiskFilename(); if (diskFilename != null) { newpostfix = '_' + diskFilename; } else { newpostfix = getPostfix(); } File tmpFile; if (getBaseDirectory() == null) { // create a temporary file tmpFile = File.createTempFile(getPrefix(), newpostfix); } else { tmpFile = File.createTempFile(getPrefix(), newpostfix, new File( getBaseDirectory())); } if (deleteOnExit()) { tmpFile.deleteOnExit(); } return tmpFile; } @Override public void setContent(ByteBuf buffer) throws IOException { if (buffer == null) { throw new NullPointerException("buffer"); } try { size = buffer.readableBytes(); checkSize(size); if (definedSize > 0 && definedSize < size) { throw new IOException("Out of size: " + size + " > " + definedSize); } if (file == null) { file = tempFile(); } if (buffer.readableBytes() == 0) { // empty file if (!file.createNewFile()) { if (file.length() == 0) { return; } else { if (!file.delete() || !file.createNewFile()) { throw new IOException("file exists already: " + file); } } } return; } FileOutputStream outputStream = new FileOutputStream(file); try { FileChannel localfileChannel = outputStream.getChannel(); ByteBuffer byteBuffer = buffer.nioBuffer(); int written = 0; while (written < size) { written += localfileChannel.write(byteBuffer); } buffer.readerIndex(buffer.readerIndex() + written); localfileChannel.force(false); } finally { outputStream.close(); } setCompleted(); } finally { // Release the buffer as it was retained before and we not need a reference to it at all // See https://github.com/netty/netty/issues/1516 buffer.release(); } } @Override public void addContent(ByteBuf buffer, boolean last) throws IOException { if (buffer != null) { try { int localsize = buffer.readableBytes(); checkSize(size + localsize); if (definedSize > 0 && definedSize < size + localsize) { throw new IOException("Out of size: " + (size + localsize) + " > " + definedSize); } ByteBuffer byteBuffer = buffer.nioBufferCount() == 1 ? buffer.nioBuffer() : buffer.copy().nioBuffer(); int written = 0; if (file == null) { file = tempFile(); } if (fileChannel == null) { FileOutputStream outputStream = new FileOutputStream(file); fileChannel = outputStream.getChannel(); } while (written < localsize) { written += fileChannel.write(byteBuffer); } size += localsize; buffer.readerIndex(buffer.readerIndex() + written); } finally { // Release the buffer as it was retained before and we not need a reference to it at all // See https://github.com/netty/netty/issues/1516 buffer.release(); } } if (last) { if (file == null) { file = tempFile(); } if (fileChannel == null) { FileOutputStream outputStream = new FileOutputStream(file); fileChannel = outputStream.getChannel(); } fileChannel.force(false); fileChannel.close(); fileChannel = null; setCompleted(); } else { if (buffer == null) { throw new NullPointerException("buffer"); } } } @Override public void setContent(File file) throws IOException { if (this.file != null) { delete(); } this.file = file; size = file.length(); checkSize(size); isRenamed = true; setCompleted(); } @Override public void setContent(InputStream inputStream) throws IOException { if (inputStream == null) { throw new NullPointerException("inputStream"); } if (file != null) { delete(); } file = tempFile(); FileOutputStream outputStream = new FileOutputStream(file); int written = 0; try { FileChannel localfileChannel = outputStream.getChannel(); byte[] bytes = new byte[4096 * 4]; ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); int read = inputStream.read(bytes); while (read > 0) { byteBuffer.position(read).flip(); written += localfileChannel.write(byteBuffer); checkSize(written); read = inputStream.read(bytes); } localfileChannel.force(false); } finally { outputStream.close(); } size = written; if (definedSize > 0 && definedSize < size) { if (!file.delete()) { logger.warn("Failed to delete: {}", file); } file = null; throw new IOException("Out of size: " + size + " > " + definedSize); } isRenamed = true; setCompleted(); } @Override public void delete() { if (fileChannel != null) { try { fileChannel.force(false); fileChannel.close(); } catch (IOException e) { logger.warn("Failed to close a file.", e); } fileChannel = null; } if (! isRenamed) { if (file != null && file.exists()) { if (!file.delete()) { logger.warn("Failed to delete: {}", file); } } file = null; } } @Override public byte[] get() throws IOException { if (file == null) { return EmptyArrays.EMPTY_BYTES; } return readFrom(file); } @Override public ByteBuf getByteBuf() throws IOException { if (file == null) { return EMPTY_BUFFER; } byte[] array = readFrom(file); return wrappedBuffer(array); } @Override public ByteBuf getChunk(int length) throws IOException { if (file == null || length == 0) { return EMPTY_BUFFER; } if (fileChannel == null) { FileInputStream inputStream = new FileInputStream(file); fileChannel = inputStream.getChannel(); } int read = 0; ByteBuffer byteBuffer = ByteBuffer.allocate(length); while (read < length) { int readnow = fileChannel.read(byteBuffer); if (readnow == -1) { fileChannel.close(); fileChannel = null; break; } else { read += readnow; } } if (read == 0) { return EMPTY_BUFFER; } byteBuffer.flip(); ByteBuf buffer = wrappedBuffer(byteBuffer); buffer.readerIndex(0); buffer.writerIndex(read); return buffer; } @Override public String getString() throws IOException { return getString(HttpConstants.DEFAULT_CHARSET); } @Override public String getString(Charset encoding) throws IOException { if (file == null) { return ""; } if (encoding == null) { byte[] array = readFrom(file); return new String(array, HttpConstants.DEFAULT_CHARSET.name()); } byte[] array = readFrom(file); return new String(array, encoding.name()); } @Override public boolean isInMemory() { return false; } @Override public boolean renameTo(File dest) throws IOException { if (dest == null) { throw new NullPointerException("dest"); } if (file == null) { throw new IOException("No file defined so cannot be renamed"); } if (!file.renameTo(dest)) { // must copy IOException exception = null; FileInputStream inputStream = null; FileOutputStream outputStream = null; long chunkSize = 8196; long position = 0; try { inputStream = new FileInputStream(file); outputStream = new FileOutputStream(dest); FileChannel in = inputStream.getChannel(); FileChannel out = outputStream.getChannel(); while (position < size) { if (chunkSize < size - position) { chunkSize = size - position; } position += in.transferTo(position, chunkSize , out); } } catch (IOException e) { exception = e; } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { if (exception == null) { // Choose to report the first exception exception = e; } else { logger.warn("Multiple exceptions detected, the following will be suppressed {}", e); } } } if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { if (exception == null) { // Choose to report the first exception exception = e; } else { logger.warn("Multiple exceptions detected, the following will be suppressed {}", e); } } } } if (exception != null) { throw exception; } if (position == size) { if (!file.delete()) { logger.warn("Failed to delete: {}", file); } file = dest; isRenamed = true; return true; } else { if (!dest.delete()) { logger.warn("Failed to delete: {}", dest); } return false; } } file = dest; isRenamed = true; return true; }
Utility function
Returns:the array of bytes
/** * Utility function * @return the array of bytes */
private static byte[] readFrom(File src) throws IOException { long srcsize = src.length(); if (srcsize > Integer.MAX_VALUE) { throw new IllegalArgumentException( "File too big to be loaded in memory"); } FileInputStream inputStream = new FileInputStream(src); byte[] array = new byte[(int) srcsize]; try { FileChannel fileChannel = inputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.wrap(array); int read = 0; while (read < srcsize) { read += fileChannel.read(byteBuffer); } } finally { inputStream.close(); } return array; } @Override public File getFile() throws IOException { return file; } @Override public HttpData touch() { return this; } @Override public HttpData touch(Object hint) { return this; } }