Skip to content

Commit cb0bf0f

Browse files
TatuLundtltv
andauthored
feat: Add overloaded fromInputStream method with fileNameOverride (#24044)
Fixes #22278 --------- Co-authored-by: Tomi Virtanen <tltv@vaadin.com>
1 parent 4b16616 commit cb0bf0f

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

flow-server/src/main/java/com/vaadin/flow/server/streams/DownloadHandler.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,32 @@ static InputStreamDownloadHandler fromInputStream(
370370
return new InputStreamDownloadHandler(callback);
371371
}
372372

373+
/**
374+
* Generate a function for downloading from a generated InputStream.
375+
* <p>
376+
* <code>DownloadResponse</code> instances can be created using various
377+
* factory methods or with new operator.
378+
* <p>
379+
* File name override is appended to the download url as the logical name of
380+
* the target file.
381+
* <p>
382+
* The {@link DownloadEvent#getFileName()} of the
383+
* {@link InputStreamDownloadCallback} returns the given
384+
* {@code fileNameOverride}.
385+
*
386+
* @param callback
387+
* a function that will be called on download
388+
* @param fileNameOverride
389+
* download file name that overrides the name taken from
390+
* <code>path</code> and also used as a download request URL
391+
* postfix
392+
* @return DownloadHandler implementation for download from an input stream
393+
*/
394+
static InputStreamDownloadHandler fromInputStream(
395+
InputStreamDownloadCallback callback, String fileNameOverride) {
396+
return new InputStreamDownloadHandler(callback, fileNameOverride);
397+
}
398+
373399
/**
374400
* Generate a function for downloading from a generated InputStream with the
375401
* given progress listener.
@@ -391,4 +417,37 @@ static InputStreamDownloadHandler fromInputStream(
391417
downloadHandler.addTransferProgressListener(listener);
392418
return downloadHandler;
393419
}
420+
421+
/**
422+
* Generate a function for downloading from a generated InputStream with the
423+
* given progress listener.
424+
* <p>
425+
* <code>DownloadResponse</code> instances can be created using various
426+
* factory methods or with new operator.
427+
* <p>
428+
* File name override is appended to the download url as the logical name of
429+
* the target file.
430+
* <p>
431+
* The {@link DownloadEvent#getFileName()} of the
432+
* {@link InputStreamDownloadCallback} returns the given
433+
* {@code fileNameOverride}.
434+
*
435+
* @param callback
436+
* a function that will be called on download
437+
* @param fileNameOverride
438+
* download file name that overrides the name taken from
439+
* <code>path</code> and also used as a download request URL
440+
* postfix
441+
* @param listener
442+
* listener for transfer progress events
443+
* @return DownloadHandler implementation for download from an input stream
444+
*/
445+
static InputStreamDownloadHandler fromInputStream(
446+
InputStreamDownloadCallback callback, String fileNameOverride,
447+
TransferProgressListener listener) {
448+
InputStreamDownloadHandler downloadHandler = new InputStreamDownloadHandler(
449+
callback, fileNameOverride);
450+
downloadHandler.addTransferProgressListener(listener);
451+
return downloadHandler;
452+
}
394453
}

flow-server/src/main/java/com/vaadin/flow/server/streams/InputStreamDownloadHandler.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class InputStreamDownloadHandler
3333
extends AbstractDownloadHandler<InputStreamDownloadHandler> {
3434

3535
private final InputStreamDownloadCallback callback;
36+
private String fileNameOverride;
3637

3738
/**
3839
* Create an input stream download handler for given event -> response
@@ -45,13 +46,39 @@ public InputStreamDownloadHandler(InputStreamDownloadCallback callback) {
4546
this.callback = callback;
4647
}
4748

49+
/**
50+
* Create an input stream download handler for given event -> response
51+
* function.
52+
* <p>
53+
* The downloaded file fileNameOverride and download URL postfix will be set
54+
* to <code>fileNameOverride</code>.
55+
*
56+
* @param callback
57+
* serializable function for handling download
58+
* @param fileNameOverride
59+
* used as a downloaded file name as a download request URL
60+
* postfix, e.g.
61+
* <code>/VAADIN/dynamic/resource/0/5298ee8b-9686-4a5a-ae1d-b38c62767d6a/my-file.txt</code>
62+
*/
63+
public InputStreamDownloadHandler(InputStreamDownloadCallback callback,
64+
String fileNameOverride) {
65+
this.callback = callback;
66+
this.fileNameOverride = fileNameOverride;
67+
}
68+
4869
@Override
4970
public void handleDownloadRequest(DownloadEvent downloadEvent)
5071
throws IOException {
5172
VaadinResponse response = downloadEvent.getResponse();
5273
setTransferUI(downloadEvent.getUI());
5374
DownloadResponse download;
5475
try {
76+
String resourceName = getUrlPostfix();
77+
if (isInline()) {
78+
downloadEvent.inline(resourceName);
79+
} else {
80+
downloadEvent.setFileName(resourceName);
81+
}
5582
download = callback.complete(downloadEvent);
5683
} catch (IOException | RuntimeException e) {
5784
// Set status before output is closed (see #8740)
@@ -109,4 +136,9 @@ public void handleDownloadRequest(DownloadEvent downloadEvent)
109136
throw ioe;
110137
}
111138
}
139+
140+
@Override
141+
public String getUrlPostfix() {
142+
return fileNameOverride;
143+
}
112144
}

flow-server/src/test/java/com/vaadin/flow/server/streams/InputStreamDownloadHandlerTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,50 @@ public void onError(TransferContext context, IOException reason) {
139139
Mockito.verify(response).setContentType("application/octet-stream");
140140
}
141141

142+
@Test
143+
void transferProgressListener_withFileNameOverride_listenersInvoked()
144+
throws URISyntaxException, IOException {
145+
List<String> invocations = new ArrayList<>();
146+
DownloadHandler handler = DownloadHandler.fromInputStream(request -> {
147+
byte[] data = getBytes();
148+
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
149+
return new DownloadResponse(inputStream, request.getFileName(),
150+
"application/octet-stream", data.length);
151+
}, "downloadOverride", new TransferProgressListener() {
152+
@Override
153+
public void onStart(TransferContext context) {
154+
assertEquals("downloadOverride", context.fileName());
155+
invocations.add("onStart");
156+
}
157+
158+
@Override
159+
public void onProgress(TransferContext context,
160+
long transferredBytes, long totalBytes) {
161+
assertEquals("downloadOverride", context.fileName());
162+
invocations.add("onProgress");
163+
}
164+
165+
@Override
166+
public void onComplete(TransferContext context,
167+
long transferredBytes) {
168+
assertEquals("downloadOverride", context.fileName());
169+
invocations.add("onComplete");
170+
}
171+
172+
@Override
173+
public void onError(TransferContext context, IOException reason) {
174+
invocations.add("onError");
175+
}
176+
});
177+
178+
handler.handleDownloadRequest(downloadEvent);
179+
180+
assertEquals(
181+
List.of("onStart", "onProgress", "onProgress", "onComplete"),
182+
invocations);
183+
Mockito.verify(response).setContentType("application/octet-stream");
184+
}
185+
142186
@Test
143187
void transferProgressListener_addListener_errorOccurred_errorListenerInvoked()
144188
throws IOException {
@@ -445,6 +489,32 @@ void handleSetToInline_contentDispositionIsInlineWithFilename()
445489
"inline; filename=\"download\"");
446490
}
447491

492+
@Test
493+
void handleSetToInline_contentDispositionIsInlineWithFilenameOverride()
494+
throws IOException {
495+
InputStream stream = Mockito.mock(InputStream.class);
496+
Mockito.when(
497+
stream.read(Mockito.any(), Mockito.anyInt(), Mockito.anyInt()))
498+
.thenReturn(-1);
499+
DownloadHandler handler = DownloadHandler
500+
.fromInputStream(event -> new DownloadResponse(stream,
501+
"download", "application/octet-stream", 0),
502+
"download_file")
503+
.inline();
504+
505+
DownloadEvent event = new DownloadEvent(request, response, session,
506+
new Element("t"));
507+
Mockito.when(response.getOutputStream()).thenReturn(outputStream);
508+
Mockito.when(response.getService()).thenReturn(service);
509+
Mockito.when(service.getMimeType(Mockito.anyString()))
510+
.thenReturn("application/octet-stream");
511+
512+
handler.handleDownloadRequest(event);
513+
514+
Mockito.verify(response).setHeader("Content-Disposition",
515+
"inline; filename=\"download_file\"");
516+
}
517+
448518
@Test
449519
void contentLengthProvided_contentLengthHeaderSet() throws IOException {
450520
long expectedContentLength = 12345L;

0 commit comments

Comments
 (0)