8

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.

0

1 Answer 1

4

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.");
    }

}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.