This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/main by this push:
new 9b8b0face WW-5733 fix(jasperreports7): stop closing the response
stream before the report is written (#1923)
9b8b0face is described below
commit 9b8b0faceef7a52f4d0aebda23a5ad7c517fbfe1
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sat Sep 12 14:29:33 2026 +0200
WW-5733 fix(jasperreports7): stop closing the response stream before the
report is written (#1923)
Every exporter provider obtained the servlet output stream in a
try-with-resources block, so the stream was closed as soon as
createExporter returned, before JasperReport7Result called
exporter.exportReport(). Tomcat commits the response on close with the
bytes written so far, which is none, and drops everything written
afterwards: every export was an empty 200 with Content-Length: 0. The
tests did not notice because Spring's MockHttpServletResponse keeps
accepting writes after close.
The providers now hand the open stream to the exporter and leave it
alone; the result flushes after exporting and the container closes the
stream at the end of the request. A response wrapper that refuses
writes after close guards the ordering for all six formats.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../export/JasperReport7CsvExporterProvider.java | 3 +-
.../export/JasperReport7HtmlExporterProvider.java | 3 +-
.../export/JasperReport7PdfExporterProvider.java | 3 +-
.../export/JasperReport7RtfExporterProvider.java | 3 +-
.../export/JasperReport7XlsxExporterProvider.java | 3 +-
.../export/JasperReport7XmlExporterProvider.java | 3 +-
.../jasperreports7/JasperReport7ResultTest.java | 72 ++++++++++++++++++++++
7 files changed, 84 insertions(+), 6 deletions(-)
diff --git
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7CsvExporterProvider.java
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7CsvExporterProvider.java
index a11b51d00..582d32443 100644
---
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7CsvExporterProvider.java
+++
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7CsvExporterProvider.java
@@ -82,7 +82,8 @@ public class JasperReport7CsvExporterProvider implements
JasperReport7ExporterPr
SimpleExporterInput input = new SimpleExporterInput(jasperPrint);
exporter.setExporterInput(input);
- try (OutputStream responseStream = response.getOutputStream()) {
+ try {
+ OutputStream responseStream = response.getOutputStream();
WriterExporterOutput exporterOutput = new
SimpleWriterExporterOutput(responseStream);
exporter.setExporterOutput(exporterOutput);
} catch (IOException e) {
diff --git
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7HtmlExporterProvider.java
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7HtmlExporterProvider.java
index 995b6d946..bc4236d37 100644
---
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7HtmlExporterProvider.java
+++
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7HtmlExporterProvider.java
@@ -70,7 +70,8 @@ public class JasperReport7HtmlExporterProvider implements
JasperReport7ExporterP
SimpleExporterInput input = new SimpleExporterInput(jasperPrint);
exporter.setExporterInput(input);
- try (OutputStream responseStream = response.getOutputStream()) {
+ try {
+ OutputStream responseStream = response.getOutputStream();
SimpleHtmlExporterOutput exporterOutput = new
SimpleHtmlExporterOutput(responseStream);
HtmlResourceHandler imageHandler = new
WebHtmlResourceHandler(request.getContextPath() + imageServletUrl + "%s");
exporterOutput.setImageHandler(imageHandler);
diff --git
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7PdfExporterProvider.java
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7PdfExporterProvider.java
index 24af92ca2..24ec670b5 100644
---
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7PdfExporterProvider.java
+++
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7PdfExporterProvider.java
@@ -48,7 +48,8 @@ public class JasperReport7PdfExporterProvider implements
JasperReport7ExporterPr
SimpleExporterInput input = new SimpleExporterInput(jasperPrint);
exporter.setExporterInput(input);
- try (OutputStream responseStream = response.getOutputStream()) {
+ try {
+ OutputStream responseStream = response.getOutputStream();
OutputStreamExporterOutput exporterOutput = new
SimpleOutputStreamExporterOutput(responseStream);
exporter.setExporterOutput(exporterOutput);
} catch (IOException e) {
diff --git
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7RtfExporterProvider.java
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7RtfExporterProvider.java
index 91dc002d7..160bcfc44 100644
---
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7RtfExporterProvider.java
+++
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7RtfExporterProvider.java
@@ -48,7 +48,8 @@ public class JasperReport7RtfExporterProvider implements
JasperReport7ExporterPr
SimpleExporterInput input = new SimpleExporterInput(jasperPrint);
exporter.setExporterInput(input);
- try (OutputStream responseStream = response.getOutputStream()) {
+ try {
+ OutputStream responseStream = response.getOutputStream();
WriterExporterOutput exporterOutput = new
SimpleWriterExporterOutput(responseStream);
exporter.setExporterOutput(exporterOutput);
} catch (IOException e) {
diff --git
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XlsxExporterProvider.java
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XlsxExporterProvider.java
index cf1317f6e..49c5a51fe 100644
---
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XlsxExporterProvider.java
+++
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XlsxExporterProvider.java
@@ -48,7 +48,8 @@ public class JasperReport7XlsxExporterProvider implements
JasperReport7ExporterP
SimpleExporterInput input = new SimpleExporterInput(jasperPrint);
exporter.setExporterInput(input);
- try (OutputStream responseStream = response.getOutputStream()) {
+ try {
+ OutputStream responseStream = response.getOutputStream();
OutputStreamExporterOutput exporterOutput = new
SimpleOutputStreamExporterOutput(responseStream);
exporter.setExporterOutput(exporterOutput);
} catch (IOException e) {
diff --git
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XmlExporterProvider.java
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XmlExporterProvider.java
index be34c8ebc..b6b9ee152 100644
---
a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XmlExporterProvider.java
+++
b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XmlExporterProvider.java
@@ -48,7 +48,8 @@ public class JasperReport7XmlExporterProvider implements
JasperReport7ExporterPr
SimpleExporterInput input = new SimpleExporterInput(jasperPrint);
exporter.setExporterInput(input);
- try (OutputStream responseOutput = response.getOutputStream()) {
+ try {
+ OutputStream responseOutput = response.getOutputStream();
XmlExporterOutput exporterOutput = new
SimpleXmlExporterOutput(responseOutput);
exporter.setExporterOutput(exporterOutput);
} catch (IOException e) {
diff --git
a/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
index d7c3aadc3..630ada4f8 100644
---
a/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
+++
b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java
@@ -19,6 +19,10 @@
package org.apache.struts2.views.jasperreports7;
import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.WriteListener;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
import net.sf.jasperreports.engine.JasperCompileManager;
import org.apache.struts2.ActionContext;
import org.apache.struts2.ActionInvocation;
@@ -29,6 +33,7 @@ import org.apache.struts2.util.ClassLoaderUtil;
import org.apache.struts2.util.ValueStack;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
@@ -303,6 +308,26 @@ public class JasperReport7ResultTest extends
StrutsTestCase {
assertThat(response.getContentAsString()).contains("Qux Report");
}
+ public void testExportWritesNothingAfterClosingTheResponseStream() throws
Exception {
+ for (String format : List.of(JasperReport7Constants.FORMAT_PDF,
JasperReport7Constants.FORMAT_CSV,
+ JasperReport7Constants.FORMAT_HTML,
JasperReport7Constants.FORMAT_RTF,
+ JasperReport7Constants.FORMAT_XML,
JasperReport7Constants.FORMAT_XLSX)) {
+ // given
+ response.setCommitted(false);
+ response.reset();
+ ActionContext.getContext().withServletResponse(new
StrictCloseResponse(response));
+ result.setDataSource("{#{'firstName':'ignore',
'lastName':'ignore'}}");
+ result.setReportParameters("#{'title':'Qux'}");
+ result.setFormat(format);
+
+ // when
+ result.execute(this.invocation);
+
+ // then
+
assertThat(response.getContentAsByteArray()).as(format).isNotEmpty();
+ }
+ }
+
public void testExportToRtf() throws Exception {
// given
result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}");
@@ -386,6 +411,53 @@ public class JasperReport7ResultTest extends
StrutsTestCase {
result.setLocation(resource + ".jasper");
}
+ /**
+ * Behaves like a servlet container: once the output stream is closed,
further writes are lost.
+ */
+ private static class StrictCloseResponse extends
HttpServletResponseWrapper {
+
+ private ServletOutputStream stream;
+
+ StrictCloseResponse(HttpServletResponse response) {
+ super(response);
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+ if (stream == null) {
+ ServletOutputStream delegate = super.getOutputStream();
+ stream = new ServletOutputStream() {
+ private boolean closed;
+
+ @Override
+ public void write(int b) throws IOException {
+ if (closed) {
+ throw new IOException("Stream closed");
+ }
+ delegate.write(b);
+ }
+
+ @Override
+ public void close() throws IOException {
+ closed = true;
+ delegate.close();
+ }
+
+ @Override
+ public boolean isReady() {
+ return delegate.isReady();
+ }
+
+ @Override
+ public void setWriteListener(WriteListener writeListener) {
+ delegate.setWriteListener(writeListener);
+ }
+ };
+ }
+ return stream;
+ }
+ }
+
private static final List<Map<String, String>> JR_MAP_ARRAY_DATA_SOURCE =
Stream.<Map<String, String>>of(
new HashMap<>() {{
put("firstName", "Foo");