-
Notifications
You must be signed in to change notification settings - Fork 398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid requiring a DataSource for subreports if printWhen expression evaluates to false #403
Comments
What I can do is changing the parent report XML to something like <subreport>
<reportElement>
<printWhenExpression><![CDATA[$F{person} != null]]></printWhenExpression>
</reportElement>
<dataSourceExpression><![CDATA[$F{DATASOURCE_PERSON}]]></dataSourceExpression>
....
</subreport> and have a custom report datasource for the parent report that looks like public Object getFieldValue(JRField field) {
var name = field.getName();
return switch(name) {
case "DATASOURCE_PERSON" -> {
if (person != null) {
yield new PersonDataSource(person);
}
yield null;
}
default -> null;
}
} But now I have the same condition in XML (printWhen) and code (DataSource) which isn't ideal if changes need to be done. |
I'm not able to reproduce the behaviour you're describing. Also at code level there's a check that skips subreport data source expression evaluation when the print when expression evaluates to false, see jasperreports/jasperreports/src/net/sf/jasperreports/engine/fill/JRFillSubreport.java Line 355 in f8fd9a6
Can you post the stacktrace of the exception that you get, to confirm where it comes from? Also, what JasperReports version are you using? |
@dadza Thanks for looking into it. Because of your answer I created an example report in my application and tested both cases:
Both cases fail at runtime if the printWhen expression evaluates to false because the datasource expression is evaluated. I am using the javaflow variant of 6.20.5 currently and in the stack trace I noticed that at com.example.ExampleReportDataSource.getFieldValueCustom(ExampleReportDataSource.java:58)
at com.example.ReportDataSourceBase.getFieldValue(ReportDataSourceBase.java:116)
at net.sf.jasperreports.engine.fill.JRFillDataset.setOldValues(JRFillDataset.java:1533)
at net.sf.jasperreports.engine.fill.JRFillDataset.next(JRFillDataset.java:1434)
at net.sf.jasperreports.engine.fill.JRFillDataset.next(JRFillDataset.java:1410)
at net.sf.jasperreports.engine.fill.JRBaseFiller.next(JRBaseFiller.java:1210)
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:117)
at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:631)
at net.sf.jasperreports.engine.fill.BaseReportFiller.fill(BaseReportFiller.java:434)
at net.sf.jasperreports.engine.fill.JRFiller.fill(JRFiller.java:162)
at net.sf.jasperreports.engine.fill.JRFiller.fill(JRFiller.java:145)
at net.sf.jasperreports.engine.JasperFillManager.fill(JasperFillManager.java:758)
at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:1074) The XML of the main report looks like: <?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.20.5.final using JasperReports Library version 6.20.5-3efcf2e67f959db3888d79f73dde2dbd7acb4f8e -->
<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="ExampleReport" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="3a2cbd1b-9f27-40e1-a4ce-7de1b91e9a85">
<field name="DS_SUBREPORT_DETAIL_1" class="net.sf.jasperreports.engine.JRDataSource"/>
<field name="DS_SUBREPORT_DETAIL_2" class="net.sf.jasperreports.engine.JRDataSource"/>
<field name="MESSAGE" class="java.lang.String"/>
<background>
<band splitType="Stretch"/>
</background>
<title>
<band splitType="Stretch"/>
</title>
<pageHeader>
<band splitType="Stretch"/>
</pageHeader>
<columnHeader>
<band splitType="Stretch"/>
</columnHeader>
<detail>
<band height="120" splitType="Stretch">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<subreport>
<reportElement x="0" y="0" width="555" height="120" uuid="a95de6c7-00ce-49e6-8e35-601309a9eb48">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<printWhenExpression><![CDATA[$F{MESSAGE} != null]]></printWhenExpression>
</reportElement>
<dataSourceExpression><![CDATA[$F{DS_SUBREPORT_DETAIL_1}]]></dataSourceExpression>
<subreportExpression><![CDATA["ExampleSubReport"]]></subreportExpression>
</subreport>
</band>
<band height="120">
<printWhenExpression><![CDATA[$F{MESSAGE} != null]]></printWhenExpression>
<subreport>
<reportElement x="0" y="0" width="555" height="120" uuid="cdad5e2b-367a-4db4-8987-28b966578c55">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
</reportElement>
<dataSourceExpression><![CDATA[$F{DS_SUBREPORT_DETAIL_2}]]></dataSourceExpression>
<subreportExpression><![CDATA["ExampleSubReport"]]></subreportExpression>
</subreport>
</band>
</detail>
<columnFooter>
<band splitType="Stretch"/>
</columnFooter>
<pageFooter>
<band splitType="Stretch"/>
</pageFooter>
<summary>
<band splitType="Stretch"/>
</summary>
</jasperReport> The corresponding data source does something like: protected Object getFieldValueCustom(JRField field) throws JRException {
return switch (field.getName()) {
case "DS_SUBREPORT_DETAIL_1" -> exampleSubReportDataSourceFactory.create(getParameters(), message);
case "DS_SUBREPORT_DETAIL_2" -> exampleSubReportDataSourceFactory.create(getParameters(), message);
case "MESSAGE" -> message;
default -> throw new IllegalArgumentException();
};
} In the above code variable Seems like |
Field values are always fetched from the data source. That's irrespective of whether the fields are used in expressions or not. Therefore the only way to have the subreport data source created only when the print when expression evaluates to true is to put the data source creation in the subreport expression and not directly in the field value. I.e. something like what you had in your original post:
Here the expression is only evaluated when the print when expression is true. But if you have something like |
Thanks for your answer. So my simplified example was a bit too simple. It is quite unfortunate that always all fields are fetched from the data source no matter what. I feel like constructing a data source in the datasource expression more or less only works for simple cases. I try to keep as much code/sql out of jasper design so I can refactor code without breaking reports unintentionally. Anything in the report design is basically hidden from code refactoring so the most natural thing to do was using just a field as data source expression and provide the instance via the parent data source. In addition data sources often have dependencies so creating the data source in the data source expression can be complex, especially if dependencies are things like daos/repositories or classes that calculate something and have dependencies themselves. That is why my data sources usually have a factory that allows for providing dependencies manually as well as injecting them via a dependency injection framework like Guice. So in my situation, if I want to avoid data source creation, the next best thing I could do is using a field that provides the factory and then in jasper design I do something like
But then if someone wants to rename the |
We are not going to change existing behaviour as there are ways to achieve what you need. Thank you, |
If I define a sub report with a printWhen expression then chances are that the sub report datasource only works correctly if the printWhen expression returns true. However JasperReports still asks for a datasource instance even if the printWhen expression evaluates to false. This makes it difficult to write DataSource implementations that check their input parameters.
Consider the example:
Now if I have a parent report with
it fails at runtime because the datasource expression is evaluated without considering the printWhen expression. Thus a
PersonDataSource
withnull
parameter may be created.I am wondering if it would be possible to avoid that and only ask for datasources if they are really needed. That would save java instances and make the code cleaner. Currently custom datasources like above would always need to expect
@Nullable
parameters and check them in theirnext()
method to eventually returnfalse
. That is quite some code noise. Ideally it should be possible to say "A PersonDataSource requires a person as input and if it doesn't receive one an exception will be thrown".The text was updated successfully, but these errors were encountered: