Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load subreport resources with Jasper?

With Jasper, I use resources to load the report. So, to load the main report, I use something like :

InputStream is = getClass().getResourceAsStream("/resources/report1.jrxml");
design = JRXmlLoader.load(is);

But, if there is a subreport in report1.jrxml, how to say it is in /resources/sub.jrxml ?

like image 556
Istao Avatar asked Jan 28 '11 07:01

Istao


People also ask

What is subreport in Jasper report?

A subreport is a report included inside another report. This allows the creation of very complex layouts with different portions of a single document filled using different data sources and reports.

How do you use subreport?

In the Navigation Pane, right-click the report to which you want to add a subreport, and then click Design View. In the menu that appears, ensure that Use Control Wizards is selected. Open the Controls Gallery again, and then click Subform/Subreport. On the report, click where you want to place the subreport.


2 Answers

I did it this way:

jasperDesign = JRXmlLoader.load(rootpath + "/WEB-INF/templates/Report.jrxml");
jasperDesignSR = JRXmlLoader.load(rootpath + "/WEB-INF/templates/SubReport.jrxml");


JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
JasperReport jasperReportSR = JasperCompileManager.compileReport(jasperDesignSR);

parameters.put("SubReportParam", jasperReportSR);
JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);

"SubReportParam" would be a parameter of the type "JasperReport" as a SubreportExpression within your Report.

In the .jrxml:

<parameter name="SubReportParam" class="net.sf.jasperreports.engine.JasperReport" isForPrompting="false"/>

I don't know if You use IReport for your Design of Reports. With a right click on your subreport you should find the SubreportExpression. parameters is a map which I pass to "fillReport"

Good luck.

like image 133
lkdg Avatar answered Sep 29 '22 12:09

lkdg


I was not quite happy with lkdg's answer because I wanted to separate the concern of loading the correct file from the design as in my opinion I should not be forced to organize from where the reports are loaded at design time of the JRXML files.

Unfortunately the code of the Jasper Library is full of static references that make it hard to find the correct spot for the injection of a custom subreport loader and also some of the documentation sucks (e.g. the interface RepositoryService is completely lacking a contract documentation so I needed to guess the contract by reading calling code), but it is possible:

private static void fillReport() throws IOException, JRException {
    // The master report can be loaded just like that, because the
    // subreports will not be loaded at this point, but later when
    // report is filled.
    final JasperReport report = loadReport("masterReport.jasper");

    // The SimpleJasperReportsContext allows us to easily specify some
    // own extensions that can be injected into the fill manager. This
    // class will also delegate to the DefaultJasperReportsContext and
    // combine results. Thus all the default extensions will still be available
    SimpleJasperReportsContext jasperReportsContext = new SimpleJasperReportsContext();
    jasperReportsContext.setExtensions(
        RepositoryService.class, singletonList(new SubReportFindingRepository())
    );

    final byte[] pdf = JasperExportManager.exportReportToPdf(
        JasperFillManager
            .getInstance(jasperReportsContext)
            // carefully select the correct `fill` method here and don't
            // accidentally select one of the static ones!:
            .fill(report, YOUR_PARAMS, YOUR_CONNECTION)
    );
}

private static JasperReport loadReport(final String fileName) throws IOException, JRException {
    try(InputStream in = loadReportAsStream(fileName)) {
        return (JasperReport) JRLoader.loadObject(in);
    }
}

private static InputStream loadReportAsStream(final String fileName) {
    final String resourceName = "/package/path/to/reports/" + fileName;
    final InputStream report = CurrentClass.class.getResourceAsStream(resourceName);
    if (report == null) {
        throw new RuntimeException("Report not found: " + resourceName);
    }
    return report;
}

private static class SubReportFindingRepository implements RepositoryService {


    @Override
    public Resource getResource(final String uri) {
        return null; // Means "not found". The next RepositoryService will be tried
    }

    @Override
    public void saveResource(final String uri, final Resource resource) {
        throw new UnsupportedOperationException();
    }

    @Override
    public <K extends Resource> K getResource(final String uri, final Class<K> resourceType) {
        if (!isKnownSubReport(uri)) {
            return null; // Means "not found". The next RepositoryService will be tried
        }

        final ReportResource reportResource = new ReportResource();
        try {
            reportResource.setReport(loadReport(uri));
        } catch (IOException | JRException e) {
            throw new Error(e);
        }
        return resourceType.cast(reportResource);
    }

    private static boolean isKnownSubReport(final String uri) {
        return "subReport1.jasper".equals(uri) || "subReport2.jasper".equals(uri);
    }
}

As an alternative to the local injection you can also write a global extension. As far as I got it (I did not try) this requires the creation of a jasperreports_extension.properties file with class names that should be loaded which can include a custom repository to load the reports from. However in this case you completely loose the ability to work with conflicting configurations needed in different use cases.

like image 42
yankee Avatar answered Sep 29 '22 11:09

yankee