16
16
17
17
package org .springframework .http .codec .multipart ;
18
18
19
+ import java .nio .file .Files ;
19
20
import java .nio .file .Path ;
21
+ import java .nio .file .StandardCopyOption ;
22
+ import java .nio .file .StandardOpenOption ;
23
+ import java .util .concurrent .Callable ;
20
24
21
25
import reactor .core .publisher .Flux ;
22
26
import reactor .core .publisher .Mono ;
27
+ import reactor .core .scheduler .Scheduler ;
23
28
24
29
import org .springframework .core .io .buffer .DataBuffer ;
25
30
import org .springframework .core .io .buffer .DataBufferUtils ;
@@ -50,17 +55,40 @@ public static FormFieldPart formFieldPart(HttpHeaders headers, String value) {
50
55
}
51
56
52
57
/**
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.
54
75
* Returns {@link FilePart} if the {@code Content-Disposition} of the given
55
76
* headers contains a filename, or a "normal" {@link Part} otherwise
56
77
* @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
58
80
* @return {@link Part} or {@link FilePart}, depending on {@link HttpHeaders#getContentDisposition()}
59
81
*/
60
- public static Part part (HttpHeaders headers , Content content ) {
82
+ public static Part part (HttpHeaders headers , Path file , Scheduler scheduler ) {
61
83
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
+
63
90
91
+ private static Part partInternal (HttpHeaders headers , Content content ) {
64
92
String filename = headers .getContentDisposition ().getFilename ();
65
93
if (filename != null ) {
66
94
return new DefaultFilePart (headers , content );
@@ -142,7 +170,8 @@ public String toString() {
142
170
*/
143
171
private static class DefaultPart extends AbstractPart {
144
172
145
- private final Content content ;
173
+ protected final Content content ;
174
+
146
175
147
176
public DefaultPart (HttpHeaders headers , Content content ) {
148
177
super (headers );
@@ -191,7 +220,7 @@ public String filename() {
191
220
192
221
@ Override
193
222
public Mono <Void > transferTo (Path dest ) {
194
- return DataBufferUtils . write ( content (), dest );
223
+ return this . content . transferTo ( dest );
195
224
}
196
225
197
226
@ Override
@@ -200,7 +229,7 @@ public String toString() {
200
229
String name = contentDisposition .getName ();
201
230
String filename = contentDisposition .getFilename ();
202
231
if (name != null ) {
203
- return "DefaultFilePart{" + name () + " (" + filename + ")}" ;
232
+ return "DefaultFilePart{" + name + " (" + filename + ")}" ;
204
233
}
205
234
else {
206
235
return "DefaultFilePart{(" + filename + ")}" ;
@@ -209,4 +238,100 @@ public String toString() {
209
238
210
239
}
211
240
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
+
212
337
}
0 commit comments