Skip to content

Commit 0c7e000

Browse files
committed
Refactor Contents to DefaultParts
This commit moves the Contents abstraction into DefaultParts See gh-27613
1 parent 694db22 commit 0c7e000

File tree

3 files changed

+135
-140
lines changed

3 files changed

+135
-140
lines changed

spring-web/src/main/java/org/springframework/http/codec/multipart/Content.java

Lines changed: 0 additions & 129 deletions
This file was deleted.

spring-web/src/main/java/org/springframework/http/codec/multipart/DefaultParts.java

Lines changed: 132 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
package org.springframework.http.codec.multipart;
1818

19+
import java.nio.file.Files;
1920
import java.nio.file.Path;
21+
import java.nio.file.StandardCopyOption;
22+
import java.nio.file.StandardOpenOption;
23+
import java.util.concurrent.Callable;
2024

2125
import reactor.core.publisher.Flux;
2226
import reactor.core.publisher.Mono;
27+
import reactor.core.scheduler.Scheduler;
2328

2429
import org.springframework.core.io.buffer.DataBuffer;
2530
import org.springframework.core.io.buffer.DataBufferUtils;
@@ -50,17 +55,40 @@ public static FormFieldPart formFieldPart(HttpHeaders headers, String value) {
5055
}
5156

5257
/**
53-
* Create a new {@link Part} or {@link FilePart} with the given parameters.
58+
* Create a new {@link Part} or {@link FilePart} based on a flux of data
59+
* buffers. Returns {@link FilePart} if the {@code Content-Disposition} of
60+
* the given headers contains a filename, or a "normal" {@link Part}
61+
* otherwise.
62+
* @param headers the part headers
63+
* @param dataBuffers the content of the part
64+
* @return {@link Part} or {@link FilePart}, depending on {@link HttpHeaders#getContentDisposition()}
65+
*/
66+
public static Part part(HttpHeaders headers, Flux<DataBuffer> dataBuffers) {
67+
Assert.notNull(headers, "Headers must not be null");
68+
Assert.notNull(dataBuffers, "DataBuffers must not be null");
69+
70+
return partInternal(headers, new FluxContent(dataBuffers));
71+
}
72+
73+
/**
74+
* Create a new {@link Part} or {@link FilePart} based on the given file.
5475
* Returns {@link FilePart} if the {@code Content-Disposition} of the given
5576
* headers contains a filename, or a "normal" {@link Part} otherwise
5677
* @param headers the part headers
57-
* @param content the content of the part
78+
* @param file the file
79+
* @param scheduler the scheduler used for reading the file
5880
* @return {@link Part} or {@link FilePart}, depending on {@link HttpHeaders#getContentDisposition()}
5981
*/
60-
public static Part part(HttpHeaders headers, Content content) {
82+
public static Part part(HttpHeaders headers, Path file, Scheduler scheduler) {
6183
Assert.notNull(headers, "Headers must not be null");
62-
Assert.notNull(content, "Content must not be null");
84+
Assert.notNull(file, "File must not be null");
85+
Assert.notNull(scheduler, "Scheduler must not be null");
86+
87+
return partInternal(headers, new FileContent(file, scheduler));
88+
}
89+
6390

91+
private static Part partInternal(HttpHeaders headers, Content content) {
6492
String filename = headers.getContentDisposition().getFilename();
6593
if (filename != null) {
6694
return new DefaultFilePart(headers, content);
@@ -142,7 +170,8 @@ public String toString() {
142170
*/
143171
private static class DefaultPart extends AbstractPart {
144172

145-
private final Content content;
173+
protected final Content content;
174+
146175

147176
public DefaultPart(HttpHeaders headers, Content content) {
148177
super(headers);
@@ -191,7 +220,7 @@ public String filename() {
191220

192221
@Override
193222
public Mono<Void> transferTo(Path dest) {
194-
return DataBufferUtils.write(content(), dest);
223+
return this.content.transferTo(dest);
195224
}
196225

197226
@Override
@@ -200,7 +229,7 @@ public String toString() {
200229
String name = contentDisposition.getName();
201230
String filename = contentDisposition.getFilename();
202231
if (name != null) {
203-
return "DefaultFilePart{" + name() + " (" + filename + ")}";
232+
return "DefaultFilePart{" + name + " (" + filename + ")}";
204233
}
205234
else {
206235
return "DefaultFilePart{(" + filename + ")}";
@@ -209,4 +238,100 @@ public String toString() {
209238

210239
}
211240

241+
242+
/**
243+
* Part content abstraction.
244+
*/
245+
private interface Content {
246+
247+
Flux<DataBuffer> content();
248+
249+
Mono<Void> transferTo(Path dest);
250+
251+
Mono<Void> delete();
252+
253+
}
254+
255+
/**
256+
* {@code Content} implementation based on a flux of data buffers.
257+
*/
258+
private static final class FluxContent implements Content {
259+
260+
private final Flux<DataBuffer> content;
261+
262+
263+
public FluxContent(Flux<DataBuffer> content) {
264+
this.content = content;
265+
}
266+
267+
268+
@Override
269+
public Flux<DataBuffer> content() {
270+
return this.content;
271+
}
272+
273+
@Override
274+
public Mono<Void> transferTo(Path dest) {
275+
return DataBufferUtils.write(this.content, dest);
276+
}
277+
278+
@Override
279+
public Mono<Void> delete() {
280+
return Mono.empty();
281+
}
282+
283+
}
284+
285+
286+
/**
287+
* {@code Content} implementation based on a file.
288+
*/
289+
private static final class FileContent implements Content {
290+
291+
private final Path file;
292+
293+
private final Scheduler scheduler;
294+
295+
296+
public FileContent(Path file, Scheduler scheduler) {
297+
this.file = file;
298+
this.scheduler = scheduler;
299+
}
300+
301+
302+
@Override
303+
public Flux<DataBuffer> content() {
304+
return DataBufferUtils.readByteChannel(
305+
() -> Files.newByteChannel(this.file, StandardOpenOption.READ),
306+
DefaultDataBufferFactory.sharedInstance, 1024)
307+
.subscribeOn(this.scheduler);
308+
}
309+
310+
@Override
311+
public Mono<Void> transferTo(Path dest) {
312+
return blockingOperation(() -> Files.copy(this.file, dest, StandardCopyOption.REPLACE_EXISTING));
313+
}
314+
315+
@Override
316+
public Mono<Void> delete() {
317+
return blockingOperation(() -> {
318+
Files.delete(this.file);
319+
return null;
320+
});
321+
}
322+
323+
private Mono<Void> blockingOperation(Callable<?> callable) {
324+
return Mono.<Void>create(sink -> {
325+
try {
326+
callable.call();
327+
sink.success();
328+
}
329+
catch (Exception ex) {
330+
sink.error(ex);
331+
}
332+
})
333+
.subscribeOn(this.scheduler);
334+
}
335+
}
336+
212337
}

spring-web/src/main/java/org/springframework/http/codec/multipart/PartGenerator.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ else if (!this.streaming) {
160160
requestToken();
161161
}
162162
});
163-
emitPart(DefaultParts.part(headers, Content.fromFlux(streamingContent)));
163+
emitPart(DefaultParts.part(headers, streamingContent));
164164
}
165165
}
166166

@@ -518,7 +518,7 @@ private void emitMemoryPart() {
518518
}
519519
this.content.clear();
520520
Flux<DataBuffer> content = Flux.just(DefaultDataBufferFactory.sharedInstance.wrap(bytes));
521-
emitPart(DefaultParts.part(this.headers, Content.fromFlux(content)));
521+
emitPart(DefaultParts.part(this.headers, content));
522522
}
523523

524524
@Override
@@ -674,8 +674,7 @@ public void body(DataBuffer dataBuffer) {
674674
@Override
675675
public void partComplete(boolean finalPart) {
676676
MultipartUtils.closeChannel(this.channel);
677-
emitPart(DefaultParts.part(this.headers,
678-
Content.fromFile(this.file, PartGenerator.this.blockingOperationScheduler)));
677+
emitPart(DefaultParts.part(this.headers, this.file, PartGenerator.this.blockingOperationScheduler));
679678
if (finalPart) {
680679
emitComplete();
681680
}

0 commit comments

Comments
 (0)