/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* Licensed 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.
* #L%
*/
package org.wisdom.framework.vertx.file;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServerFileUpload;
import org.slf4j.LoggerFactory;
import org.wisdom.api.http.Result;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* A smart implementation of {@link org.wisdom.api.http.FileItem} adapting the uploaded file storage according to the
* upload size. As this size cannot be determined beforehand, it first tries to use a {@link org.wisdom.framework
* .vertx.file.MemoryFileUpload}, and if a threshold is reached, it changes to {@link org.wisdom.framework.vertx.file
* .DiskFileUpload}.
*/
public class MixedFileUpload extends VertxFileUpload {
/**
* The current instance of {@link org.wisdom.api.http.FileItem}. The value change when a amount of data uploaded
* reached a threshold.
*/
VertxFileUpload delegate;
/**
* Creates an instance of {@link org.wisdom.framework.vertx.file.VertxFileUpload}.
*
* @param vertx the Vert.X instance
* @param upload the upload object
* @param limitSize the threshold. If the amount of uploaded data is below this limit,
* {@link org.wisdom.framework.vertx.file.MemoryFileUpload} is used to backend the uploaded file.
* Otherwise, it uses a {@link org.wisdom.framework.vertx.file.DiskFileUpload}.
*/
public MixedFileUpload(final Vertx vertx,
final HttpServerFileUpload upload,
final long limitSize,
final long maxSize,
final Handler<Result> errorHandler) {
super(upload, errorHandler);
delegate = new MemoryFileUpload(upload, errorHandler);
upload.exceptionHandler(event -> LoggerFactory.getLogger(MixedFileUpload.class)
.error("Cannot read the uploaded item {} ({})", upload.name(), upload.filename(), event))
.endHandler(event -> delegate.close())
.handler(
event -> {
if (event != null) {
// We are still in memory.
if (delegate instanceof MemoryFileUpload) {
MemoryFileUpload mem = (MemoryFileUpload) delegate;
checkSize(mem.buffer.length() + event.length(), maxSize, upload);
if (mem.buffer.length() + event.length() > limitSize) {
// Switch to disk file upload.
DiskFileUpload disk = new DiskFileUpload(vertx, upload, errorHandler);
// Initial push (mem + current buffer)
disk.push(mem.buffer.appendBuffer(event));
// No cleanup required for the memory based backend.
delegate = disk;
// the disk based implementation use a pump.
} else {
delegate.push(event);
}
}
}
}
);
}
/**
* Checks whether we exceed the max allowed file size.
*
* @param newSize the expected size once the current chunk is consumed
* @param maxSize the max allowed size.
* @param upload the upload
*/
private void checkSize(long newSize, long maxSize, HttpServerFileUpload upload) {
if (maxSize >= 0 && newSize > maxSize) {
upload.handler(null);
report(new IllegalStateException("Size exceed allowed maximum capacity"));
}
}
/**
* Delegated to the wrapped backend.
*/
@Override
public void cleanup() {
delegate.cleanup();
}
/**
* Gets the bytes.
*
* @return the full content of the file.
*/
@Override
public byte[] bytes() {
return delegate.bytes();
}
/**
* Opens an input stream on the file.
*
* @return an input stream to read the content of the uploaded item.
*/
@Override
public InputStream stream() {
return delegate.stream();
}
/**
* Provides a hint as to whether or not the file contents will be read from memory.
*
* @return {@literal true} if the file content is in memory.
*/
@Override
public boolean isInMemory() {
return delegate.isInMemory();
}
/**
* Gets a {@link java.io.File} object for this uploaded file. This file is a <strong>temporary</strong> file.
* Depending on how is handled the file upload, the file may already exist, or not (in-memory) and then is created.
*
* @return a file object
* @since 0.7.1
*/
@Override
public File toFile() throws IOException {
return delegate.toFile();
}
}