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>

Reply via email to