I have reactive API that takes Mono as an argument as show below
Mono<FileType> processAndReturnType(Mono<FilePart> partFlux)
I have to call this from another API which takes file as
getType(MultipartFile partFile)
The calling api is non reactive and how can I convert MultipartFile to FilePart.
One of the way is below which works fine and but raises a security concern/ sonar issue as file is available in temp directory -
I am wondering what could be the best way to handle this. If I can directly stream. Any suggestion will help
package com.maersk.clm.naas.tenderfile.convertor;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.lang.NonNull;
import reactor.core.scheduler.Schedulers;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
/**
* Implementation of {@link FilePart} that wraps a {@link MultipartFile}.
*/
public class MultipartFilePart implements FilePart {
private final MultipartFile file;
public MultipartFilePart(MultipartFile file) {
this.file = file;
}
@Override
@NonNull
public Flux<DataBuffer> content() {
try {
Path tempFile = Files.createTempFile("upload", "temp");
file.transferTo(tempFile);
return DataBufferUtils.read(tempFile, new DefaultDataBufferFactory(), (int) file.getSize());
} catch (IOException e) {
return Flux.error(e);
}
}
@Override
@NonNull
public String filename() {
return Objects.requireNonNull(file.getOriginalFilename());
}
@Override
@NonNull
public String name() {
return file.getName();
}
@Override
@NonNull
public Mono<Void> transferTo(@NonNull Path dest) {
return DataBufferUtils.write(content(), dest)
.then()
.publishOn(Schedulers.boundedElastic())
.doFinally(signalType -> {
try {
Files.deleteIfExists(dest);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
@Override
@NonNull
public HttpHeaders headers() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(Objects.requireNonNull(file.getContentType())));
return headers;
}
}
I tried different ways to convert but couldnt any better solution
You can do without the temp file. MultipartFile
has two other ways to get to its contents: getBytes()
returns a byte[]
and getInputStream()
returns an InputStream
. You can use both to get a Flux
.
DataBufferUtils
has method readInputStream that's easy to use:
return DataBufferUtils.readInputStream(
file::getInputStream,
new DefaultDataBufferFactory(),
(int) file.getSize());
With getBytes()
you can use the factory directly:
return Flux.just(new DefaultDataBufferFactory().wrap(file.getBytes());
Out of these 3 options I'd go for the readInputStream
one. The temp file involves disk I/O, and the file needs to be deleted, something you forgot to do. The byte[]
option needs to read the entire contents to memory synchronously before it can be wrapped. The readInputStream
option on the other hand can even work with a smaller buffer size.
Once you get this working I'd see if it's possible to create a reusable DataBufferFactory
. DefaultDataBufferFactory.sharedInstance
may be an option.