This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch WW-3245-jasper-parameter-fill in repository https://gitbox.apache.org/repos/asf/struts.git
commit 3b8b8adc369de5fa34f34c3d2cbf0eb496b6edcc Author: Lukasz Lenart <[email protected]> AuthorDate: Sat Sep 12 10:32:09 2026 +0200 WW-3245 feat(jasperreports): fill from report parameters when no dataSource or connection is set Both result types insisted on either dataSource or connection and only ever called the three-argument JasperFillManager.fillReport overloads, so a report whose data comes from a JasperReports query executer (Hibernate, CSV, JSON, EJBQL, ...) could not be filled without first materialising a List in the action. When neither is configured the result now calls fillReport(report, params) and lets JasperReports resolve the data from the parameter map exactly as its standalone API does: the executer's supplier object (HIBERNATE_SESSION, CSV_INPUT_STREAM, ...) or a ready REPORT_DATA_SOURCE / REPORT_CONNECTION is handed over via reportParameters. No parameter-key inspection and no new dependency, so every present and future executer works the same way. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- .../views/jasperreports/JasperReportsResult.java | 24 +++++++------- .../jasperreports/JasperReportsResultTest.java | 28 ++++++++++++++-- .../apache/struts2/views/jasperreports/csv.jrxml | 38 ++++++++++++++++++++++ .../views/jasperreports7/JasperReport7Result.java | 23 +++++++------ .../jasperreports7/JasperReport7ResultTest.java | 32 ++++++++++++++++-- .../apache/struts2/views/jasperreports7/csv.jrxml | 37 +++++++++++++++++++++ 6 files changed, 155 insertions(+), 27 deletions(-) diff --git a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java index f24ddd1b8..b014c9ecc 100644 --- a/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java +++ b/plugins/jasperreports/src/main/java/org/apache/struts2/views/jasperreports/JasperReportsResult.java @@ -72,8 +72,13 @@ import java.util.TimeZone; * * <li><b>location (default)</b> - the location where the compiled jasper report * definition is (foo.jasper), relative from current URL.</li> - * <li><b>dataSource (required)</b> - the EL expression used to retrieve the - * datasource from the value stack (usually a List).</li> + * <li><b>dataSource</b> - the EL expression used to retrieve the + * datasource from the value stack (usually a List). When neither dataSource + * nor connection is set the report is filled from its parameters alone, + * so a data supplier expected by the report's query executer (e.g. + * <code>HIBERNATE_SESSION</code>, <code>CSV_INPUT_STREAM</code>, + * <code>JSON_INPUT_STREAM</code>) or a ready <code>REPORT_DATA_SOURCE</code> / + * <code>REPORT_CONNECTION</code> can be handed over via reportParameters.</li> * <li><b>parse</b> - true by default. If set to false, the location param will * not be parsed for EL expressions.</li> * <li><b>format</b> - the format in which the report should be generated. Valid @@ -258,7 +263,6 @@ public class JasperReportsResult extends StrutsResultSupport implements JasperRe } protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { - // Will throw a runtime exception if no "datasource" property. TODO Best place for that is...? initializeProperties(invocation); LOG.debug("Creating JasperReport for dataSource = {}, format = {}", dataSource, format); @@ -284,7 +288,7 @@ public class JasperReportsResult extends StrutsResultSupport implements JasperRe ValueStackDataSource stackDataSource = null; Connection conn = (Connection) stack.findValue(connection); - if (conn == null) { + if (conn == null && dataSource != null) { boolean evaluated = parsedDataSource != null && !parsedDataSource.equals(dataSource); boolean reevaluate = !evaluated || isAcceptableExpression(parsedDataSource); if (reevaluate) { @@ -338,10 +342,13 @@ public class JasperReportsResult extends StrutsResultSupport implements JasperRe // Fill the report and produce a print object try { JasperReport jasperReport = (JasperReport) JRLoader.loadObject(new File(systemId)); - if (conn == null) { + if (conn != null) { + jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn); + } else if (stackDataSource != null) { jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, stackDataSource); } else { - jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, conn); + LOG.debug("No dataSource or connection set, filling {} from report parameters only", systemId); + jasperPrint = JasperFillManager.fillReport(jasperReport, parameters); } } catch (JRException e) { LOG.error("Error building report for uri {}", systemId, e); @@ -460,11 +467,6 @@ public class JasperReportsResult extends StrutsResultSupport implements JasperRe * @throws Exception on initialization error. */ private void initializeProperties(ActionInvocation invocation) { - if (dataSource == null && connection == null) { - String message = "No dataSource specified..."; - LOG.error(message); - throw new RuntimeException(message); - } if (dataSource != null) { parsedDataSource = conditionalParse(dataSource, invocation); } diff --git a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java index 1fb31763e..beb563c12 100644 --- a/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java +++ b/plugins/jasperreports/src/test/java/org/apache/struts2/views/jasperreports/JasperReportsResultTest.java @@ -28,7 +28,9 @@ import org.apache.struts2.StrutsStatics; import org.apache.struts2.junit.StrutsTestCase; import jakarta.servlet.ServletException; +import java.io.ByteArrayInputStream; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.util.HashMap; import java.util.Map; @@ -169,6 +171,21 @@ public class JasperReportsResultTest extends StrutsTestCase { assertTrue(response.getContentAsString().contains("Qux Report")); } + public void testFillFromReportParametersWithoutDataSourceOrConnection() throws Exception { + stack.push(new Object() { + public Map<String, Object> getReportParameters() { + return Map.of("CSV_INPUT_STREAM", + new ByteArrayInputStream("Foo,Bar\n".getBytes(StandardCharsets.UTF_8))); + } + }); + result.setReportParameters("reportParameters"); + compileAndUseReport("csv.jrxml"); + + result.execute(this.invocation); + + assertTrue(response.getContentAsString().contains("Hello Foo Bar!")); + } + public void testExportParametersNotAccepted() throws Exception { result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); @@ -234,12 +251,17 @@ public class JasperReportsResultTest extends StrutsTestCase { result = new JasperReportsResult(); container.inject(result); - URL url = ClassLoaderUtil.getResource("org/apache/struts2/views/jasperreports/simple.jrxml", this.getClass()); - JasperCompileManager.compileReportToFile(url.getFile(), url.getFile() + ".jasper"); - result.setLocation("org/apache/struts2/views/jasperreports/simple.jrxml.jasper"); + compileAndUseReport("simple.jrxml"); result.setFormat(JasperReportConstants.FORMAT_XML); } + private void compileAndUseReport(String jrxml) throws Exception { + String resource = "org/apache/struts2/views/jasperreports/" + jrxml; + URL url = ClassLoaderUtil.getResource(resource, this.getClass()); + JasperCompileManager.compileReportToFile(url.getFile(), url.getFile() + ".jasper"); + result.setLocation(resource + ".jasper"); + } + private static final Map<String, String>[] JR_MAP_ARRAY_DATA_SOURCE = new Map[]{ new HashMap<String, String>() {{ diff --git a/plugins/jasperreports/src/test/resources/org/apache/struts2/views/jasperreports/csv.jrxml b/plugins/jasperreports/src/test/resources/org/apache/struts2/views/jasperreports/csv.jrxml new file mode 100644 index 000000000..f235420b9 --- /dev/null +++ b/plugins/jasperreports/src/test/resources/org/apache/struts2/views/jasperreports/csv.jrxml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +--> +<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" + name="csv" pageWidth="842" pageHeight="595" orientation="Landscape" columnWidth="802"> + <property name="net.sf.jasperreports.csv.column.names" value="firstName,lastName"/> + <queryString language="csv"><![CDATA[]]></queryString> + <field name="firstName" /> + <field name="lastName" /> + <detail> + <band height="16"> + <textField> + <reportElement x="0" y="0" width="100" height="16" /> + <textFieldExpression>"Hello " + <![CDATA[$F{firstName}]]> + " " + <![CDATA[$F{lastName}]]> + "!"</textFieldExpression> + </textField> + </band> + </detail> +</jasperReport> diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Result.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Result.java index e8d26bb82..fde8590bc 100644 --- a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Result.java +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Result.java @@ -61,8 +61,13 @@ import java.util.TimeZone; * * <li><b>location (default)</b> - the location where the compiled jasper report * definition is (foo.jasper), relative from current URL.</li> - * <li><b>dataSource (required)</b> - the EL expression used to retrieve the - * datasource from the value stack (usually a List).</li> + * <li><b>dataSource</b> - the EL expression used to retrieve the + * datasource from the value stack (usually a List). When neither dataSource + * nor connection is set the report is filled from its parameters alone, + * so a data supplier expected by the report's query executer (e.g. + * <code>HIBERNATE_SESSION</code>, <code>CSV_INPUT_STREAM</code>, + * <code>JSON_INPUT_STREAM</code>) or a ready <code>REPORT_DATA_SOURCE</code> / + * <code>REPORT_CONNECTION</code> can be handed over via reportParameters.</li> * <li><b>parse</b> - true by default. If set to false, all the parameters will * not be parsed for EL expressions.</li> * <li><b>format</b> - the format in which the report should be generated. Valid @@ -161,7 +166,7 @@ public class JasperReport7Result extends StrutsResultSupport implements JasperRe ValueStack stack = invocation.getStack(); Connection reportConnection = (Connection) stack.findValue(connection); ValueStackDataSource reportDataSource = null; - if (reportConnection == null) { + if (reportConnection == null && dataSource != null) { reportDataSource = prepareDataSource(stack); } @@ -185,10 +190,13 @@ public class JasperReport7Result extends StrutsResultSupport implements JasperRe // Fill the report and produce a print object try { JasperReport jasperReport = (JasperReport) JRLoader.loadObject(new File(systemId)); - if (reportConnection == null) { + if (reportConnection != null) { + jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, reportConnection); + } else if (reportDataSource != null) { jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, reportDataSource); } else { - jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, reportConnection); + LOG.debug("No dataSource or connection set, filling {} from report parameters only", systemId); + jasperPrint = JasperFillManager.fillReport(jasperReport, parameters); } if (invocation.getAction() instanceof JasperReport7Aware action) { @@ -305,11 +313,6 @@ public class JasperReport7Result extends StrutsResultSupport implements JasperRe * @param invocation Current invocation. */ private void initializeProperties(ActionInvocation invocation) { - if (dataSource == null && connection == null) { - String message = "No dataSource specified..."; - LOG.error(message); - throw new RuntimeException(message); - } if (dataSource != null) { parsedDataSource = conditionalParse(dataSource, invocation); } 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 38d3fe27e..d1f82d5dc 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 @@ -27,7 +27,9 @@ import org.apache.struts2.security.NotExcludedAcceptedPatternsChecker; import org.apache.struts2.util.ClassLoaderUtil; import org.apache.struts2.util.ValueStack; +import java.io.ByteArrayInputStream; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.util.HashMap; import java.util.List; @@ -195,6 +197,25 @@ public class JasperReport7ResultTest extends StrutsTestCase { assertThat(response.getContentAsString()).contains("Baz Report"); } + public void testFillFromReportParametersWithoutDataSourceOrConnection() throws Exception { + // given + stack.push(new Object() { + public Map<String, Object> getReportParameters() { + return Map.of("CSV_INPUT_STREAM", + new ByteArrayInputStream("Foo,Bar\n".getBytes(StandardCharsets.UTF_8))); + } + }); + result.setReportParameters("reportParameters"); + compileAndUseReport("csv.jrxml"); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/xml"); + assertThat(response.getContentAsString()).contains("Hello Foo Bar!"); + } + public void testExportToXml() throws Exception { // given result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); @@ -295,12 +316,17 @@ public class JasperReport7ResultTest extends StrutsTestCase { result = new JasperReport7Result(); container.inject(result); - URL url = ClassLoaderUtil.getResource("org/apache/struts2/views/jasperreports7/simple.jrxml", this.getClass()); - JasperCompileManager.compileReportToFile(url.getFile(), url.getFile() + ".jasper"); - result.setLocation("org/apache/struts2/views/jasperreports7/simple.jrxml.jasper"); + compileAndUseReport("simple.jrxml"); result.setFormat(JasperReport7Constants.FORMAT_XML); } + private void compileAndUseReport(String jrxml) throws Exception { + String resource = "org/apache/struts2/views/jasperreports7/" + jrxml; + URL url = ClassLoaderUtil.getResource(resource, this.getClass()); + JasperCompileManager.compileReportToFile(url.getFile(), url.getFile() + ".jasper"); + result.setLocation(resource + ".jasper"); + } + private static final List<Map<String, String>> JR_MAP_ARRAY_DATA_SOURCE = Stream.<Map<String, String>>of( new HashMap<>() {{ put("firstName", "Foo"); diff --git a/plugins/jasperreports7/src/test/resources/org/apache/struts2/views/jasperreports7/csv.jrxml b/plugins/jasperreports7/src/test/resources/org/apache/struts2/views/jasperreports7/csv.jrxml new file mode 100644 index 000000000..b8e966286 --- /dev/null +++ b/plugins/jasperreports7/src/test/resources/org/apache/struts2/views/jasperreports7/csv.jrxml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +--> +<jasperReport name="CsvReport" language="java" pageWidth="842" pageHeight="595" orientation="Landscape" columnWidth="802" + uuid="5a2a5a9e-4c9c-4f3e-9c1c-6a1a7b1f2d30"> + <property name="net.sf.jasperreports.csv.column.names" value="firstName,lastName"/> + <query language="csv"><![CDATA[]]></query> + <field name="firstName"/> + <field name="lastName"/> + + <detail> + <band height="16"> + <element kind="textField" x="0" y="0" width="100" height="16" + uuid="9a7c6d2e-2b3f-4d5e-8f60-1b2c3d4e5f60"> + <expression>"Hello " + <![CDATA[$F{firstName}]]> + " " + <![CDATA[$F{lastName}]]> + "!"</expression> + </element> + </band> + </detail> +</jasperReport>
