writeWith(Path file, long position, long count) {
+ return doCommit(() -> this.outbound.sendFile(file, position, count).then());
}
@Override
diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/FilePart.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/FilePart.java
index e1237ce76e..df05a5b2fa 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/multipart/FilePart.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/FilePart.java
@@ -17,6 +17,7 @@
package org.springframework.http.codec.multipart;
import java.io.File;
+import java.nio.file.Path;
import reactor.core.publisher.Mono;
@@ -25,6 +26,7 @@ import reactor.core.publisher.Mono;
* a multipart request.
*
* @author Rossen Stoyanchev
+ * @author Juergen Hoeller
* @since 5.0
*/
public interface FilePart extends Part {
@@ -38,10 +40,26 @@ public interface FilePart extends Part {
* Convenience method to copy the content of the file in this part to the
* given destination file. If the destination file already exists, it will
* be truncated first.
+ * The default implementation delegates to {@link #transferTo(Path)}.
* @param dest the target file
* @return completion {@code Mono} with the result of the file transfer,
* possibly {@link IllegalStateException} if the part isn't a file
+ * @see #transferTo(Path)
*/
- Mono transferTo(File dest);
+ default Mono transferTo(File dest) {
+ return transferTo(dest.toPath());
+ }
+
+ /**
+ * Convenience method to copy the content of the file in this part to the
+ * given destination file. If the destination file already exists, it will
+ * be truncated first.
+ * @param dest the target file
+ * @return completion {@code Mono} with the result of the file transfer,
+ * possibly {@link IllegalStateException} if the part isn't a file
+ * @since 5.1
+ * @see #transferTo(File)
+ */
+ Mono transferTo(Path dest);
}
diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java
index 0a362dfc3e..4e4974b194 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java
@@ -16,7 +16,6 @@
package org.springframework.http.codec.multipart;
-import java.io.File;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
@@ -24,6 +23,7 @@ import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.OpenOption;
+import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import java.util.List;
@@ -90,19 +90,14 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader
@Override
- public Flux read(ResolvableType elementType, ReactiveHttpInputMessage message,
- Map hints) {
-
+ public Flux read(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) {
return Flux.create(new SynchronossPartGenerator(message, this.bufferFactory, this.streamStorageFactory));
}
@Override
- public Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage message,
- Map hints) {
-
- return Mono.error(new UnsupportedOperationException(
- "Can't read a multipart request body into a single Part."));
+ public Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) {
+ return Mono.error(new UnsupportedOperationException("Cannot read multipart request body into single Part"));
}
@@ -118,15 +113,14 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader
private final PartBodyStreamStorageFactory streamStorageFactory;
-
SynchronossPartGenerator(ReactiveHttpInputMessage inputMessage, DataBufferFactory bufferFactory,
PartBodyStreamStorageFactory streamStorageFactory) {
+
this.inputMessage = inputMessage;
this.bufferFactory = bufferFactory;
this.streamStorageFactory = streamStorageFactory;
}
-
@Override
public void accept(FluxSink emitter) {
HttpHeaders headers = this.inputMessage.getHeaders();
@@ -189,14 +183,12 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader
private final AtomicInteger terminated = new AtomicInteger(0);
-
FluxSinkAdapterListener(FluxSink sink, DataBufferFactory factory, MultipartContext context) {
this.sink = sink;
this.bufferFactory = factory;
this.context = context;
}
-
@Override
public void onPartFinished(StreamStorage storage, Map> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
@@ -250,16 +242,14 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader
private final DataBufferFactory bufferFactory;
-
AbstractSynchronossPart(HttpHeaders headers, DataBufferFactory bufferFactory) {
Assert.notNull(headers, "HttpHeaders is required");
- Assert.notNull(bufferFactory, "'bufferFactory' is required");
+ Assert.notNull(bufferFactory, "DataBufferFactory is required");
this.name = MultipartUtils.getFieldName(headers);
this.headers = headers;
this.bufferFactory = bufferFactory;
}
-
@Override
public String name() {
return this.name;
@@ -280,14 +270,12 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader
private final StreamStorage storage;
-
SynchronossPart(HttpHeaders headers, StreamStorage storage, DataBufferFactory factory) {
super(headers, factory);
- Assert.notNull(storage, "'storage' is required");
+ Assert.notNull(storage, "StreamStorage is required");
this.storage = storage;
}
-
@Override
public Flux content() {
return DataBufferUtils.readInputStream(getStorage()::getInputStream, getBufferFactory(), 4096);
@@ -301,33 +289,28 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader
private static class SynchronossFilePart extends SynchronossPart implements FilePart {
- private static final OpenOption[] FILE_CHANNEL_OPTIONS = {
- StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE };
-
+ private static final OpenOption[] FILE_CHANNEL_OPTIONS =
+ {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE};
private final String filename;
-
- SynchronossFilePart(HttpHeaders headers, String filename, StreamStorage storage,
- DataBufferFactory factory) {
-
+ SynchronossFilePart(HttpHeaders headers, String filename, StreamStorage storage, DataBufferFactory factory) {
super(headers, storage, factory);
this.filename = filename;
}
-
@Override
public String filename() {
return this.filename;
}
@Override
- public Mono transferTo(File destination) {
+ public Mono transferTo(Path dest) {
ReadableByteChannel input = null;
FileChannel output = null;
try {
input = Channels.newChannel(getStorage().getInputStream());
- output = FileChannel.open(destination.toPath(), FILE_CHANNEL_OPTIONS);
+ output = FileChannel.open(dest, FILE_CHANNEL_OPTIONS);
long size = (input instanceof FileChannel ? ((FileChannel) input).size() : Long.MAX_VALUE);
long totalWritten = 0;
while (totalWritten < size) {
@@ -366,13 +349,11 @@ public class SynchronossPartHttpMessageReader implements HttpMessageReader
private final String content;
-
SynchronossFormFieldPart(HttpHeaders headers, DataBufferFactory bufferFactory, String content) {
super(headers, bufferFactory);
this.content = content;
}
-
@Override
public String value() {
return this.content;
diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java
index c46908cd4d..591924dce1 100644
--- a/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java
+++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ReactorServerHttpResponse.java
@@ -16,7 +16,7 @@
package org.springframework.http.server.reactive;
-import java.io.File;
+import java.nio.file.Path;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpResponseStatus;
@@ -112,8 +112,8 @@ class ReactorServerHttpResponse extends AbstractServerHttpResponse implements Ze
}
@Override
- public Mono writeWith(File file, long position, long count) {
- return doCommit(() -> this.response.sendFile(file.toPath(), position, count).then());
+ public Mono writeWith(Path file, long position, long count) {
+ return doCommit(() -> this.response.sendFile(file, position, count).then());
}
private static Publisher toByteBufs(Publisher extends DataBuffer> dataBuffers) {
diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java
index 58aed42f29..b7aa467ab8 100644
--- a/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java
+++ b/spring-web/src/main/java/org/springframework/http/server/reactive/UndertowServerHttpResponse.java
@@ -16,10 +16,10 @@
package org.springframework.http.server.reactive;
-import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
+import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import io.undertow.server.HttpServerExchange;
@@ -106,10 +106,10 @@ class UndertowServerHttpResponse extends AbstractListenerServerHttpResponse impl
}
@Override
- public Mono writeWith(File file, long position, long count) {
+ public Mono writeWith(Path file, long position, long count) {
return doCommit(() ->
Mono.defer(() -> {
- try (FileChannel source = FileChannel.open(file.toPath(), StandardOpenOption.READ)) {
+ try (FileChannel source = FileChannel.open(file, StandardOpenOption.READ)) {
StreamSinkChannel destination = this.exchange.getResponseChannel();
Channels.transferBlocking(destination, source, position, count);
return Mono.empty();
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java
index fdfdec68fd..09ee1372e8 100644
--- a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java
+++ b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java
@@ -19,10 +19,13 @@ package org.springframework.web.multipart;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
+import org.springframework.util.FileCopyUtils;
/**
* A representation of an uploaded file received in a multipart request.
@@ -127,4 +130,15 @@ public interface MultipartFile extends InputStreamSource {
*/
void transferTo(File dest) throws IOException, IllegalStateException;
+ /**
+ * Transfer the received file to the given destination file.
+ * The default implementation simply copies the file input stream.
+ * @since 5.1
+ * @see #getInputStream()
+ * @see #transferTo(File)
+ */
+ default void transferTo(Path dest) throws IOException, IllegalStateException {
+ FileCopyUtils.copy(getInputStream(), Files.newOutputStream(dest));
+ }
+
}
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java
index def7861d7c..73e0e021a4 100644
--- a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java
+++ b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFileResource.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.web.multipart;
import java.io.IOException;
@@ -35,7 +36,7 @@ class MultipartFileResource extends AbstractResource {
public MultipartFileResource(MultipartFile multipartFile) {
- Assert.notNull(multipartFile, "MultipartFile must not be null.");
+ Assert.notNull(multipartFile, "MultipartFile must not be null");
this.multipartFile = multipartFile;
}
@@ -86,9 +87,8 @@ class MultipartFileResource extends AbstractResource {
@Override
public boolean equals(Object obj) {
- return (obj == this ||
- (obj instanceof MultipartFileResource &&
- ((MultipartFileResource) obj).multipartFile.equals(this.multipartFile)));
+ return (obj == this || (obj instanceof MultipartFileResource &&
+ ((MultipartFileResource) obj).multipartFile.equals(this.multipartFile)));
}
@Override
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java
index a483fe8507..e1c9585e7e 100644
--- a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java
+++ b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java
@@ -20,6 +20,8 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
+import java.nio.file.Files;
+import java.nio.file.Path;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
@@ -27,6 +29,7 @@ import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;
@@ -185,6 +188,15 @@ public class CommonsMultipartFile implements MultipartFile, Serializable {
}
}
+ @Override
+ public void transferTo(Path dest) throws IOException, IllegalStateException {
+ if (!isAvailable()) {
+ throw new IllegalStateException("File has already been moved - cannot be transferred again");
+ }
+
+ FileCopyUtils.copy(this.fileItem.getInputStream(), Files.newOutputStream(dest));
+ }
+
/**
* Determine whether the multipart content is still available.
* If a temporary file has been moved, the content is no longer available.
diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java b/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java
index fa6ae7dbda..c0273ff82c 100644
--- a/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java
+++ b/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -263,6 +264,11 @@ public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpSe
FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest.toPath()));
}
}
+
+ @Override
+ public void transferTo(Path dest) throws IOException, IllegalStateException {
+ FileCopyUtils.copy(this.part.getInputStream(), Files.newOutputStream(dest));
+ }
}