Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java : InputStream to Multi-part file conversion, result file is empty

I am working on a Java application in which I am trying to create a Multipart file out of downloaded InputStream. Unfortunately, it is not working and the Multipart file is empty. I checked the size of savedFile on disk before copying it to Multipart, and it has correct size, attributes, content.

What am I doing wrong in the conversion, there is no stacktrace, as I am catching it.

Code :

// InputStream contains file data.
byte[] bytes = IOUtils.toByteArray(inputStream);

File file = new File(msg + "temp");
if (file.exists() && file.isDirectory()) {
  OutputStream outputStream = new FileOutputStream(new File(msg + "temp" + "/" +
    groupAttachments.getFileName()));
  outputStream.write(bytes);
  outputStream.close();
}
java.io.File savedFile = new java.io.File(msg + "temp" + "/" + 
  groupAttachments.getFileName());
DiskFileItem fileItem = new DiskFileItem("file", "text/plain", false,
                                            savedFile.getName(), (int) savedFile.length(), savedFile.getParentFile());
fileItem.getOutputStream();
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);

System.out.println("Saved file size is "+savedFile.length());
if (multipartFile.isEmpty()) {
  System.out.println("Dropbox uploaded multipart file is empty");
} else {
  System.out.println("Multipart file is not empty.");
}
this.dropboxTask.insertFile(multipartFile, "",
  savedPersonalNoteObject.getNoteid(), (long) 0, true);
Path path = Paths.get(msg + "temp" + "/" + groupAttachments.getFileName());

Console output :

Multipart file is not empty
Bytes are not null
File path is /My Group
Input stream is not null
Saved file size is 4765
Dropbox uploaded multipart file is empty
Multipart file is empty
Bytes are not null

What am I doing wrong in the conversion? Any help would be nice. Thanks a lot.

like image 783
We are Borg Avatar asked Sep 26 '22 06:09

We are Borg


1 Answers

The DiskFileItem uses a DeferredFileOutputStream which uses an in-memory byte-array that is only filled when bytes are actually transferred. Since files are used directly and no bytes are actually copied, the byte-array is never filled. See for yourself in the source code:
Source code CommonsMultipartFile
Source code DiskFileItem
Source code DeferredFileOutputStream

So, instead of just calling fileItem.getOutputStream();, transfer the bytes to fill the in-memory byte-array:

try (OutputStream out = fileItem.getOutputStream();
        InputStream in = Files.newInputStream(file.toPath())) {
    IOUtils.copy(in, dfos);
}

and then the tranferTo call will work.
This appears to be a bit cumbersome for just moving a file: CommonsMultipartFile only calls fileItem.write((File)dest) in the transferTo method. Below are two test cases, one using the DiskFileItem and one using the LocalFileItem. The code for LocalFileItem is shown further below.
I used dependencies org.springframework:spring-web:4.2.2.RELEASE, commons-fileupload:commons-fileupload:1.3.1 and junit:junit:4.12
Test class CommonMp:

import static org.junit.Assert.*;
import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;

import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

public class CommonMp {

    private final Charset CS = StandardCharsets.UTF_8;

    @Test
    public void testLocalMp() {

        Path testInputFile = null, testOutputFile = null;
        try {
            testInputFile = prepareInputFile();
            LocalFileItem lfi = new LocalFileItem(testInputFile);
            CommonsMultipartFile cmf = new CommonsMultipartFile(lfi);
            System.out.println("Empty: " + cmf.isEmpty());
            testOutputFile = testInputFile.getParent().resolve("testMpOutput.txt");
            cmf.transferTo(testOutputFile.toFile());
            System.out.println("Size: " + cmf.getSize());
            printOutput(testOutputFile);
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        } finally {
            deleteSilent(testInputFile, testOutputFile);
        }
    }

    @Test
    public void testMp() {

        Path testInputFile = null, testOutputFile = null;
        try {
            testInputFile = prepareInputFile();
            DiskFileItem di = new DiskFileItem("file", "text/plain", false, testInputFile.getFileName().toString(), 
                    (int) Files.size(testInputFile), testInputFile.getParent().toFile());
            try (OutputStream out = di.getOutputStream();
                    InputStream in = Files.newInputStream(testInputFile)) {
                IOUtils.copy(in, out);
            }
            CommonsMultipartFile cmf = new CommonsMultipartFile(di);
            System.out.println("Size: " + cmf.getSize());
            testOutputFile = testInputFile.getParent().resolve("testMpOutput.txt");
            cmf.transferTo(testOutputFile.toFile());
            printOutput(testOutputFile);
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        } finally {
            deleteSilent(testInputFile, testOutputFile);
        }
    }

    private Path prepareInputFile() throws IOException {

        Path tmpDir = Paths.get(System.getProperty("java.io.tmpdir"));
        Path testInputFile = tmpDir.resolve("testMpinput.txt");
        try (OutputStream out = Files.newOutputStream(testInputFile)){
            out.write("Just a test.".getBytes(CS));
        }
        return testInputFile;
    }

    private void printOutput(Path p) throws IOException {

        byte[] outBytes = Files.readAllBytes(p);
        System.out.println("Output: " + new String(outBytes, CS));
    }

    private void deleteSilent(Path... paths) {

        for (Path p : paths) {
            try { if (p != null) p.toFile().delete(); } catch (Exception ignored) {}
        }
    }

}

The custom LocalFileItem class, YMMV!

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemHeaders;

public class LocalFileItem implements FileItem {

    private static final long serialVersionUID = 2467880290855097332L;

    private final Path localFile;

    public LocalFileItem(Path localFile) {
        this.localFile = localFile;
    }

    @Override
    public void write(File file) throws Exception {
        Files.move(localFile, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
    }

    @Override
    public long getSize() {

        // Spring's CommonsMultipartFile caches the file size and uses it to determine availability.
        long size = -1L;
        try {
            size = Files.size(localFile);
        } catch (IOException ignored) {}
        return size;
    }

    @Override
    public void delete() {
        localFile.toFile().delete();
    }

    /* *** properties and unsupported methods *** */

    private FileItemHeaders headers;
    private String contentType;
    private String fieldName;
    private boolean formField;

    @Override
    public FileItemHeaders getHeaders() {
        return headers;
    }

    @Override
    public void setHeaders(FileItemHeaders headers) {
        this.headers = headers;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        throw new IOException("Only method write(File) is supported.");
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    @Override
    public String getContentType() {
        return contentType;
    }

    @Override
    public String getName() {
        return localFile.getFileName().toString();
    }

    @Override
    public boolean isInMemory() {
        return false;
    }

    @Override
    public byte[] get() {
        throw new RuntimeException("Only method write(File) is supported.");
    }

    @Override
    public String getString(String encoding)
            throws UnsupportedEncodingException {
        throw new RuntimeException("Only method write(File) is supported.");
    }

    @Override
    public String getString() {
        throw new RuntimeException("Only method write(File) is supported.");
    }

    @Override
    public String getFieldName() {
        return fieldName;
    }

    @Override
    public void setFieldName(String name) {
        this.fieldName = name;
    }

    @Override
    public boolean isFormField() {
        return formField;
    }

    @Override
    public void setFormField(boolean state) {
        this.formField = state;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new IOException("Only method write(File) is supported.");
    }

}
like image 91
vanOekel Avatar answered Oct 10 '22 17:10

vanOekel