Skip to content

Commit 02b1507

Browse files
authored
feat: add DownloadHandler (#21246)
Add DownloadHandler interface and factory methods. Closes #21166
1 parent db2d501 commit 02b1507

File tree

14 files changed

+1349
-2
lines changed

14 files changed

+1349
-2
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package com.vaadin.flow.server;
18+
19+
import java.io.File;
20+
import java.util.Optional;
21+
22+
import com.vaadin.flow.dom.Element;
23+
import com.vaadin.flow.function.SerializableFunction;
24+
import com.vaadin.flow.server.streams.ClassDownloadHandler;
25+
import com.vaadin.flow.server.streams.DownloadResponse;
26+
import com.vaadin.flow.server.streams.FileDownloadHandler;
27+
import com.vaadin.flow.server.streams.InputStreamDownloadHandler;
28+
import com.vaadin.flow.server.streams.ServletResourceDownloadHandler;
29+
30+
/**
31+
* Interface for handling download of data from the server to the client.
32+
*
33+
* @since 24.8
34+
*/
35+
@FunctionalInterface
36+
public interface DownloadHandler extends ElementRequestHandler {
37+
38+
/**
39+
* Method that is called when the client wants to download from the url
40+
* stored for this specific handler registration.
41+
*
42+
* @param event
43+
* download event containing the necessary data for writing the
44+
* response
45+
*/
46+
void handleDownloadRequest(DownloadRequest event);
47+
48+
default void handleRequest(VaadinRequest request, VaadinResponse response,
49+
VaadinSession session, Element owner) {
50+
String fileName = getUrlPostfix() == null ? "" : getUrlPostfix();
51+
52+
DownloadRequest event = new DownloadRequest(request, response, session,
53+
fileName);
54+
event.withOwningComponent(owner)
55+
.withContentType(Optional
56+
.ofNullable(response.getService().getMimeType(fileName))
57+
.orElse("application/octet-stream"));
58+
59+
handleDownloadRequest(event);
60+
}
61+
62+
/**
63+
* Get a download handler for serving given {@link File}.
64+
*
65+
* @param file
66+
* file to server for download
67+
* @return DownloadHandler instance for file
68+
*/
69+
static DownloadHandler forFile(File file) {
70+
return new FileDownloadHandler(file);
71+
}
72+
73+
/**
74+
* Get a download handler for serving given {@link File}.
75+
*
76+
* @param file
77+
* file to server for download
78+
* @param name
79+
* download name to use
80+
* @return DownloadHandler instance for file
81+
*/
82+
static DownloadHandler forFile(File file, String name) {
83+
return new FileDownloadHandler(file, name);
84+
}
85+
86+
/**
87+
* Generate a download handler for class resource.
88+
* <p>
89+
* For instance for the file {@code resources/com/example/ui/MyData.json}
90+
* and class {@code com.example.ui.MyData} the definition would be
91+
* {@code forClassResource(MyData.class, "MyData.json")}
92+
*
93+
* @param clazz
94+
* class for resource module
95+
* @param resourceName
96+
* name of class resource
97+
* @return DownloadHandler instance for class resource
98+
*/
99+
static DownloadHandler forClassResource(Class<?> clazz,
100+
String resourceName) {
101+
return new ClassDownloadHandler(clazz, resourceName);
102+
}
103+
104+
/**
105+
* Generate a download handler for class resource.
106+
* <p>
107+
* For instance for the file {@code resources/com/example/ui/MyData.json}
108+
* and class {@code com.example.ui.MyData} the definition would be
109+
* {@code forClassResource(MyData.class, "MyData.json", "Data.json")}
110+
*
111+
* @param clazz
112+
* class for resource module
113+
* @param resourceName
114+
* name of class resource
115+
* @param fileName
116+
* download resourceName to use
117+
* @return DownloadHandler instance for class resource
118+
*/
119+
static DownloadHandler forClassResource(Class<?> clazz, String resourceName,
120+
String fileName) {
121+
return new ClassDownloadHandler(clazz, resourceName, fileName);
122+
}
123+
124+
/**
125+
* Generate a download handler for a servlet resource.
126+
* <p>
127+
* For instance for the file {@code webapp/WEB-INF/servlet.json} the path
128+
* would be {@code /WEB-INF/servlet.json}
129+
*
130+
* @param path
131+
* the servlet path to the file
132+
* @return DownloadHandler instance for servlet resource
133+
*/
134+
static DownloadHandler forServletResource(String path) {
135+
return new ServletResourceDownloadHandler(path);
136+
}
137+
138+
/**
139+
* Generate a download handler for a servlet resource.
140+
* <p>
141+
* For instance for the file {@code webapp/WEB-INF/servlet.json} the path
142+
* would be {@code /WEB-INF/servlet.json}
143+
* <p>
144+
* Name is appended to the download url as the logical name of the target
145+
* file.
146+
*
147+
* @param path
148+
* the servlet path to the file
149+
* @param name
150+
* resource name
151+
* @return DownloadHandler instance for servlet resource
152+
*/
153+
static DownloadHandler forServletResource(String path, String name) {
154+
return new ServletResourceDownloadHandler(path, name);
155+
}
156+
157+
/**
158+
* Generate a function for downloading from a generated inputStream.
159+
*
160+
* @param handler
161+
* handler function that will be called on download
162+
* @return DownloadHandler instance for inputStream
163+
*/
164+
static DownloadHandler fromInputStream(
165+
SerializableFunction<DownloadRequest, DownloadResponse> handler) {
166+
return new InputStreamDownloadHandler(handler);
167+
}
168+
169+
/**
170+
* Generate a function for downloading from a generated inputStream.
171+
*
172+
* @param handler
173+
* handler function that will be called on download
174+
* @param name
175+
* resource name
176+
* @return DownloadHandler instance for inputStream
177+
*/
178+
static DownloadHandler fromInputStream(
179+
SerializableFunction<DownloadRequest, DownloadResponse> handler,
180+
String name) {
181+
return new InputStreamDownloadHandler(handler, name);
182+
}
183+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/*
2+
* Copyright 2000-2025 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package com.vaadin.flow.server;
18+
19+
import java.io.IOException;
20+
import java.io.OutputStream;
21+
import java.io.PrintWriter;
22+
import java.io.Serializable;
23+
import java.io.UncheckedIOException;
24+
import java.util.Optional;
25+
26+
import org.slf4j.LoggerFactory;
27+
28+
import com.vaadin.flow.component.Component;
29+
import com.vaadin.flow.dom.Element;
30+
31+
/**
32+
* Class containing data on requested client download.
33+
*
34+
* @since 24.8
35+
*/
36+
public class DownloadRequest implements Serializable {
37+
38+
private final VaadinRequest request;
39+
private final VaadinResponse response;
40+
private final VaadinSession session;
41+
42+
private final String fileName;
43+
private String contentType;
44+
45+
private Component owningComponent;
46+
47+
/**
48+
* Create a new download event with required data.
49+
*
50+
* @param request
51+
* current request
52+
* @param response
53+
* current response to write response data to
54+
* @param session
55+
* current session
56+
* @param fileName
57+
* defined download file name
58+
*/
59+
public DownloadRequest(VaadinRequest request, VaadinResponse response,
60+
VaadinSession session, String fileName) {
61+
this.request = request;
62+
this.response = response;
63+
this.session = session;
64+
this.fileName = fileName;
65+
}
66+
67+
/**
68+
* Set the owning component for the download event from the element instance
69+
* if a component is available.
70+
*
71+
* @param owningElement
72+
* owning element for the event
73+
* @return this Event instance
74+
*/
75+
DownloadRequest withOwningComponent(Element owningElement) {
76+
if (owningElement != null) {
77+
Optional<Component> component = owningElement.getComponent();
78+
component.ifPresent(this::withOwningComponent);
79+
}
80+
return this;
81+
}
82+
83+
/**
84+
* Set the owning component for the download event.
85+
*
86+
* @param owningComponent
87+
* owning component for the event
88+
* @return this Event instance
89+
*/
90+
DownloadRequest withOwningComponent(Component owningComponent) {
91+
this.owningComponent = owningComponent;
92+
return this;
93+
}
94+
95+
/**
96+
* Set the DownloadEvent content type.
97+
*
98+
* @param contentType
99+
* content type of the event content
100+
* @return this Event instance
101+
*/
102+
DownloadRequest withContentType(String contentType) {
103+
this.contentType = contentType;
104+
return this;
105+
}
106+
107+
/**
108+
* Returns a <code>OutputStream</code> for writing binary data in the
109+
* response.
110+
* <p>
111+
* Either this method or getWriter() may be called to write the response,
112+
* not both.
113+
*
114+
* @return a <code>OutputStream</code> for writing binary data or empty
115+
* optional if an error happened
116+
*/
117+
public OutputStream getOutputStream() {
118+
try {
119+
return response.getOutputStream();
120+
} catch (IOException e) {
121+
LoggerFactory.getLogger(DownloadRequest.class)
122+
.error("Error getting output stream", e);
123+
throw new UncheckedIOException("Error getting output stream", e);
124+
}
125+
}
126+
127+
/**
128+
* Returns a <code>PrintWriter</code> object that can send character text to
129+
* the client. The PrintWriter uses the character encoding defined using
130+
* setContentType.
131+
* <p>
132+
* Either this method or getOutputStream() may be called to write the
133+
* response, not both.
134+
*
135+
* @return a <code>PrintWriter</code> for writing character text or empty
136+
* optional if an error happened
137+
*/
138+
public PrintWriter getWriter() {
139+
try {
140+
return response.getWriter();
141+
} catch (IOException e) {
142+
LoggerFactory.getLogger(DownloadRequest.class)
143+
.error("Error getting print writer");
144+
throw new UncheckedIOException("Error getting writer", e);
145+
}
146+
}
147+
148+
/**
149+
* Get {@link VaadinRequest} for download event.
150+
*
151+
* @return vaadin request
152+
*/
153+
public VaadinRequest getRequest() {
154+
return request;
155+
}
156+
157+
/**
158+
* Get {@link VaadinResponse} for download event.
159+
*
160+
* @return vaadin response
161+
*/
162+
public VaadinResponse getResponse() {
163+
return response;
164+
}
165+
166+
/**
167+
* Get {@link VaadinSession} for download event.
168+
*
169+
* @return vaadin session
170+
*/
171+
public VaadinSession getSession() {
172+
return session;
173+
}
174+
175+
/**
176+
* Get the set file name.
177+
*
178+
* @return file name
179+
*/
180+
public String getFileName() {
181+
return fileName == null ? "" : fileName;
182+
}
183+
184+
/**
185+
* Get the content type for the data to download.
186+
*
187+
* @return set content type
188+
*/
189+
public String getContentType() {
190+
return contentType;
191+
}
192+
193+
/**
194+
* Get owner {@link Component} for this event.
195+
*
196+
* @return owning component or null in none defined
197+
*/
198+
public Component getOwningComponent() {
199+
return owningComponent;
200+
}
201+
}

0 commit comments

Comments
 (0)