Skip to content

Commit 694db22

Browse files
committed
Add Part::delete method
This commit introduces the Part::delete method, that deletes its underlying storage. Closes gh-27612
1 parent 47d3819 commit 694db22

File tree

5 files changed

+189
-18
lines changed

5 files changed

+189
-18
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.codec.multipart;
18+
19+
import java.io.IOException;
20+
import java.io.UncheckedIOException;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.nio.file.StandardOpenOption;
24+
25+
import reactor.core.publisher.Flux;
26+
import reactor.core.publisher.Mono;
27+
import reactor.core.scheduler.Scheduler;
28+
29+
import org.springframework.core.io.buffer.DataBuffer;
30+
import org.springframework.core.io.buffer.DataBufferUtils;
31+
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
32+
33+
/**
34+
* Part content abstraction used by {@link DefaultParts}.
35+
*
36+
* @author Arjen Poutsma
37+
* @since 5.3.13
38+
*/
39+
abstract class Content {
40+
41+
42+
protected Content() {
43+
}
44+
45+
/**
46+
* Return the content.
47+
*/
48+
public abstract Flux<DataBuffer> content();
49+
50+
/**
51+
* Delete this content. Default implementation does nothing.
52+
*/
53+
public Mono<Void> delete() {
54+
return Mono.empty();
55+
}
56+
57+
/**
58+
* Returns a new {@code Content} based on the given flux of data buffers.
59+
*/
60+
public static Content fromFlux(Flux<DataBuffer> content) {
61+
return new FluxContent(content);
62+
}
63+
64+
/**
65+
* Return a new {@code Content} based on the given file path.
66+
*/
67+
public static Content fromFile(Path file, Scheduler scheduler) {
68+
return new FileContent(file, scheduler);
69+
}
70+
71+
72+
/**
73+
* {@code Content} implementation based on a flux of data buffers.
74+
*/
75+
private static final class FluxContent extends Content {
76+
77+
private final Flux<DataBuffer> content;
78+
79+
80+
public FluxContent(Flux<DataBuffer> content) {
81+
this.content = content;
82+
}
83+
84+
85+
@Override
86+
public Flux<DataBuffer> content() {
87+
return this.content;
88+
}
89+
}
90+
91+
92+
/**
93+
* {@code Content} implementation based on a file.
94+
*/
95+
private static final class FileContent extends Content {
96+
97+
private final Path file;
98+
99+
private final Scheduler scheduler;
100+
101+
102+
public FileContent(Path file, Scheduler scheduler) {
103+
this.file = file;
104+
this.scheduler = scheduler;
105+
}
106+
107+
108+
@Override
109+
public Flux<DataBuffer> content() {
110+
return DataBufferUtils.readByteChannel(
111+
() -> Files.newByteChannel(this.file, StandardOpenOption.READ),
112+
DefaultDataBufferFactory.sharedInstance, 1024)
113+
.subscribeOn(this.scheduler);
114+
}
115+
116+
@Override
117+
public Mono<Void> delete() {
118+
return Mono.<Void>fromRunnable(() -> {
119+
try {
120+
Files.delete(this.file);
121+
}
122+
catch (IOException ex) {
123+
throw new UncheckedIOException(ex);
124+
}
125+
})
126+
.subscribeOn(this.scheduler);
127+
}
128+
}
129+
}

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static FormFieldPart formFieldPart(HttpHeaders headers, String value) {
5757
* @param content the content of the part
5858
* @return {@link Part} or {@link FilePart}, depending on {@link HttpHeaders#getContentDisposition()}
5959
*/
60-
public static Part part(HttpHeaders headers, Flux<DataBuffer> content) {
60+
public static Part part(HttpHeaders headers, Content content) {
6161
Assert.notNull(headers, "Headers must not be null");
6262
Assert.notNull(content, "Content must not be null");
6363

@@ -142,16 +142,21 @@ public String toString() {
142142
*/
143143
private static class DefaultPart extends AbstractPart {
144144

145-
private final Flux<DataBuffer> content;
145+
private final Content content;
146146

147-
public DefaultPart(HttpHeaders headers, Flux<DataBuffer> content) {
147+
public DefaultPart(HttpHeaders headers, Content content) {
148148
super(headers);
149149
this.content = content;
150150
}
151151

152152
@Override
153153
public Flux<DataBuffer> content() {
154-
return this.content;
154+
return this.content.content();
155+
}
156+
157+
@Override
158+
public Mono<Void> delete() {
159+
return this.content.delete();
155160
}
156161

157162
@Override
@@ -171,9 +176,9 @@ public String toString() {
171176
/**
172177
* Default implementation of {@link FilePart}.
173178
*/
174-
private static class DefaultFilePart extends DefaultPart implements FilePart {
179+
private static final class DefaultFilePart extends DefaultPart implements FilePart {
175180

176-
public DefaultFilePart(HttpHeaders headers, Flux<DataBuffer> content) {
181+
public DefaultFilePart(HttpHeaders headers, Content content) {
177182
super(headers, content);
178183
}
179184

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.http.codec.multipart;
1818

1919
import reactor.core.publisher.Flux;
20+
import reactor.core.publisher.Mono;
2021

2122
import org.springframework.core.io.buffer.DataBuffer;
2223
import org.springframework.http.HttpHeaders;
@@ -57,4 +58,13 @@ public interface Part {
5758
*/
5859
Flux<DataBuffer> content();
5960

61+
/**
62+
* Return a mono that, when subscribed to, deletes the underlying storage
63+
* for this part.
64+
* @since 5.3.13
65+
*/
66+
default Mono<Void> delete() {
67+
return Mono.empty();
68+
}
69+
6070
}

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

Lines changed: 4 additions & 12 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, streamingContent));
163+
emitPart(DefaultParts.part(headers, Content.fromFlux(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));
521+
emitPart(DefaultParts.part(this.headers, Content.fromFlux(content)));
522522
}
523523

524524
@Override
@@ -674,21 +674,13 @@ public void body(DataBuffer dataBuffer) {
674674
@Override
675675
public void partComplete(boolean finalPart) {
676676
MultipartUtils.closeChannel(this.channel);
677-
Flux<DataBuffer> content = partContent();
678-
emitPart(DefaultParts.part(this.headers, content));
677+
emitPart(DefaultParts.part(this.headers,
678+
Content.fromFile(this.file, PartGenerator.this.blockingOperationScheduler)));
679679
if (finalPart) {
680680
emitComplete();
681681
}
682682
}
683683

684-
private Flux<DataBuffer> partContent() {
685-
return DataBufferUtils
686-
.readByteChannel(
687-
() -> Files.newByteChannel(this.file, StandardOpenOption.READ),
688-
DefaultDataBufferFactory.sharedInstance, 1024)
689-
.subscribeOn(PartGenerator.this.blockingOperationScheduler);
690-
}
691-
692684
@Override
693685
public void dispose() {
694686
if (this.closeOnDispose) {

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

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

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

19+
import java.io.File;
1920
import java.io.IOException;
21+
import java.io.InputStream;
2022
import java.nio.channels.Channels;
2123
import java.nio.channels.FileChannel;
2224
import java.nio.channels.ReadableByteChannel;
@@ -40,6 +42,7 @@
4042
import org.synchronoss.cloud.nio.multipart.NioMultipartParser;
4143
import org.synchronoss.cloud.nio.multipart.NioMultipartParserListener;
4244
import org.synchronoss.cloud.nio.multipart.PartBodyStreamStorageFactory;
45+
import org.synchronoss.cloud.nio.stream.storage.NameAwarePurgableFileInputStream;
4346
import org.synchronoss.cloud.nio.stream.storage.StreamStorage;
4447
import reactor.core.publisher.BaseSubscriber;
4548
import reactor.core.publisher.Flux;
@@ -497,6 +500,38 @@ public Flux<DataBuffer> content() {
497500
protected StreamStorage getStorage() {
498501
return this.storage;
499502
}
503+
504+
@Override
505+
public Mono<Void> delete() {
506+
return Mono.fromRunnable(() -> {
507+
File file = getFile();
508+
if (file != null) {
509+
file.delete();
510+
}
511+
});
512+
}
513+
514+
@Nullable
515+
private File getFile() {
516+
InputStream inputStream = null;
517+
try {
518+
inputStream = getStorage().getInputStream();
519+
if (inputStream instanceof NameAwarePurgableFileInputStream) {
520+
NameAwarePurgableFileInputStream stream = (NameAwarePurgableFileInputStream) inputStream;
521+
return stream.getFile();
522+
}
523+
}
524+
finally {
525+
if (inputStream != null) {
526+
try {
527+
inputStream.close();
528+
}
529+
catch (IOException ignore) {
530+
}
531+
}
532+
}
533+
return null;
534+
}
500535
}
501536

502537

0 commit comments

Comments
 (0)