Hot questions for Using JasperReports in spring boot

Question:

I'm generating a multi-language report using JasperReports in Java.

When I generate pdf, few languages do not display properly.

Whereas, When I generate Excel, The report is generated properly with the correct language.

searching on the internet I found pdf font is not supported.

here my code set to font:

 Style rowStyle = new Style();
 Font font = new Font(FONT_SIZE_SMALL, "Noto Sans", false, false, false);
 font.setPdfFontName("Noto Sans");
 font.setPdfFontEncoding(Font.PDF_ENCODING_Identity_H_Unicode_with_horizontal_writing);
 font.setPdfFontEmbedded(false);
 rowStyle.setFont(font);

any idea how to solve?


Answer:

Have you tried setting DEBUG jasper reports logging to check which font is getting used when the PDF is rendered? (example log4j setup below)

<category name="net.sf.jasperreports">
    <priority value="DEBUG" />
</category>

For example you should see DEBUG log of a font being loaded by jasper:

DEBUG SimpleFontFace:177 - Loading font fonts/ARIALUNI.TTF

And if you are using jasper font extension (described below), you should see something like:

 DEBUG FontExtensionsRegistry:88 - Loading font extensions from net/sf/jasperreports/fonts/jasperreports-fonts.xml

Are you including the font in the classpath? I have successfully used a custom built jar similar to what is described in this post in the docs: https://community.jaspersoft.com/wiki/adding-fonts-embedding-pdf

I would also try setPdfFontEmbedded(true). In the generated PDF you can determine if the font is included in the report if you open the PDF in Adobe Acrobat Reader, and see if it is listed under File -> Properties... -> Fonts tab (see screenshot). It should have (Embedded) or (Embedded subset) next to the font name.

Edit

Above steps can help debug issues like this. It turns out that the "Noto Sans" font doesn't support Indian characters, using "Arial Unicode MS" works though.

In Docs, you can see how many languages are supported.

Question:

I want to be able to send report generated with JasperReports as attachment. Currently in my application I am able to view the report using JasperReportsViewResolver below is my configuration

@Bean
public JasperReportsViewResolver getJasperReportsViewResolver() {
  JasperReportsViewResolver resolver = new JasperReportsViewResolver();
  resolver.setPrefix("classpath:/static/jasper/");
  resolver.setSuffix(".jrxml");
  resolver.setReportDataKey("datasource");
  resolver.setViewNames("rpt_*");
  resolver.setViewClass(JasperReportsMultiFormatView.class);
  resolver.setOrder(0);
  return resolver;
}  

I was not able to get the input resource from this viewresolver so below is the idea I'm thinking of but I'm not sure it is the best way

 JasperDesign jasperDesign =  JRXmlLoader.load(getClass().getResourceAsStream("/jasper/rpt_media.jrxml"));
 JasperReport report = JasperCompileManager.compileReport(jasperDesign);
 JRDataSource reportData = new JREmptyDataSource();

 Map<String, Object> parameters = new HashMap<String, Object>();
 parameters.put("reportTitle", "dfa dafdf d dfd");

 OutputStream stream = new FileOutputStream(tempfile);

 JasperReportsUtils.renderAsPdf(report, parameters, reportData, stream);

after saving the report to random file I do read it and send as attachment.

Any other suggestion?


Answer:

I found the below to be the best way

    JRDataSource ds = new JRBeanCollectionDataSource(reportList);

    Resource report = new ClassPathResource("static/jasper/rpt_report.jasper");

    JasperPrint jasperPrint = JasperFillManager.fillReport(report.getInputStream(), Collections.EMPTY_MAP,ds);
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    JasperExportManager.exportReportToPdfStream(jasperPrint, baos);
    DataSource aAttachment =  new ByteArrayDataSource(baos.toByteArray(), "application/pdf");

    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message);

    helper.setTo("xxxxxx");

    helper.setFrom("xxxxx");
    helper.setSubject("Testing Email");

    String text = "Testing Email";

    helper.setText(text, false);

    helper.addAttachment("report.pdf",aAttachment);

    mailSender.send(message);

Question:


Answer:

You are using java.io file api operations to load the resources from the classpath. It works when the application in unpacked during development in IDE but will fail when the app is packaged to JAR, hence the FileNotFoundException

Change your code to load the resources from the classpath with InputStream e.g. by using JasperCompileManager.compileReport():.

try (InputStream in = getClass().getResourceAsStream("/invoices/invoice.jrxml")) {
    JasperCompileManager.compileReport(in);
    ...
}

Question:

I am using JasperReportsPdfView to generate report in my Spring Boot project. Currently I have 1 Main report and that main report has many sub report

So, Main Report -> Sub report 1, Sub report 2, etc...

I have EntityA that have Detail and Details2

now I am using JRDataSource to pass the datasource to each sub report like...

EntityA entityA = findById(1);
JasperReportsPdfView view = new JasperReportsPdfView();
view.setUrl("classpath:report/main_report.jrxml");
view.setApplicationContext(appContext);

List<Detail> details = entityA.getDetails();
List<Detail2> details2 = entityA.getDetails2();
JRDataSource subReportDetail1Source = new JRBeanCollectionDataSource(details);
JRDataSource subReportDetail2Source = new JRBeanCollectionDataSource(details2);
final Map<String, Object> params = new HashMap<>();
params.put("subReportData2", subReportDetail1Source);
params.put("subReportData3", subReportDetail2Source);

return new ModelAndView(view, params);

Now I want to create many of this Main Report for each EntityA, so I will have list of EntityA like

List<EntityA> listOfEntityA = findAll();

How can I create my Main Report repeated for each EntityA in listOfEntityA?

I have idea to make my current MainReport to become sub report to another MainReport, but I don't know the way to pass the Datasource of each detail1 and detail2


Answer:

In this case you should not pass the subReportData2 and subReportData3 as parameters put define them as fields in the jrxml.

Your main datasource will be

new JRBeanCollectionDataSource(istOfEntityA);

In you jrxml in you define as fields Detail and Detail2 list, hence the getters on EntityA

<field name="details" class="java.util.List"/>
<field name="details2" class="java.util.List"/>

Then add you subreport to the detail band and set the datasource expression as:

new net.sf.jasperreports.engine.data.JRBeanCollectionDataSource($F{details})

This way the report will iterate the detail band on your EntityA list, and call the subreports with the list of Detail and Detail2 of the EntityA class as datasource

Question:

I've been working on this for quite a while but I can't find an exact solution for what I'm searching for. In short: I have a Spring Boot MVC application, that integrates JasperReports. These reports are supposed to be resizable. Therefore, I want to read the .jrxml via the XML-loader, provided by jasper itself, manipulate that jasperDesign and pass it to the respective Spring/Jasper-view without saving it.

the jasper-config in spring:

@Configuration
public class JasperReportsConfig {

@Bean
public JasperReportsViewResolver getJasperReportsViewResolver() {
    JasperReportsViewResolver resolver = new JasperReportsViewResolver();

    resolver.setViewClass(JasperReportsMultiFormatView.class);
    resolver.setPrefix("classpath:/jrxml/");
    resolver.setSuffix(".jrxml");
    resolver.setReportDataKey("datasource");
    resolver.setViewNames("rpt_*");
    resolver.setOrder(0);

    return resolver;
}
}

the jasper-controller in spring:

@Controller
public class JasperReportsController {

//...

@RequestMapping(value = "/reportPreview", method=RequestMethod.GET, produces = "application/pdf")
public ModelAndView setupReport(ModelMap modelMap, ModelAndView modelAndView) throws IOException {
    modelMap.put("datasource", new JREmptyDataSource());
    modelMap.put("background", getbackground());
    modelMap.put("format", "pdf");

    /*modify report here*/

    //currently I pass a String ("rpt_myReport") to the viewResolver, but I need to
    //pass a JasperReportDesign, so that Spring can proceed with compiling
    modelAndView = new ModelAndView("rpt_myReport", modelMap);
    return modelAndView;
}
}

I hope that somebody might have a solution for this, despite using DynamicJasper, etc ^^

Best regards and thanks in advance.


Answer:

I'm not really sure if this is what you're looking for, but here's how I do it. (I have changed names of things to make them generic, hopefully it still compiles/works)

@RequestMapping(value="/report", params = {"objectid"}, method = { RequestMethod.GET })
public void getReport(HttpServletResponse response, @RequestParam(value = "objectid") long objectid){
    Object object = objectDAOFactory.getObjectDAO().loadById(objectid);

    ObjectDataSource dataSource = new ObjectDataSource(object);

    JasperPrint jasperPrint = getObjectPdf("pdf/ObjectReport.jrxml", new HashMap<String, Object>(), dataSource);

    sendPdfResponse(response, jasperPrint, "Object-" + object.getId());
}

public JasperPrint getObjectPdf(String path, Map<String, Object> parameters, JRDataSource dataSource) {
    JasperPrint jasperPrint = null;

    InputStream inStream = null;
    try {
        inStream = getClass().getClassLoader().getResourceAsStream(path);
        JasperDesign jasperDesign = JRXmlLoader.load(inStream);
        JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
        jasperReport.setWhenNoDataType(WhenNoDataTypeEnum.ALL_SECTIONS_NO_DETAIL);
        jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource);
    } catch (JRException jre) {
        logger.error("Error creating Pdf", jre);
    } finally {
        if (inStream != null) {
            try {
                inStream.close();
            } catch (IOException e) {
                logger.error("Error closing stream", e);
            }
        }
    }

    return jasperPrint;
}

public static void sendPdfResponse(HttpServletResponse response, JasperPrint jasperPrint, String fileName){

    //Remove all whitespace from file name
    fileName.replaceAll("\\s","");

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    try {
        JasperExportManager.exportReportToPdfStream(jasperPrint, out);
    } catch (JRException e1) {
        e1.printStackTrace();
    }

    byte[] data = out.toByteArray();

    response.setContentType("application/pdf");
    //To make it a download change "inline" to "attachment"
    response.setHeader("Content-disposition", "inline; filename=" + fileName + ".pdf");
    response.setContentLength(data.length);

    try {
        response.getOutputStream().write(data);
        response.getOutputStream().flush();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

Question:

Report structure:

main report - myreport.jrxml 
sub report  - myreport_detail.jrxml

Running my main report with sub report works fine in Jasper Report Studio. In my Spring Boot environment I use JasperReportsPdfView.

However running myreport.jrxml here I get:

Caused by: net.sf.jasperreports.engine.JRException: Resource not found at: jasper/repo/myreport_detail.jasper.
at net.sf.jasperreports.repo.RepositoryUtil.getResourceFromLocation(RepositoryUtil.java:153)
at net.sf.jasperreports.repo.RepositoryUtil.getReport(RepositoryUtil.java:112)
at net.sf.jasperreports.engine.fill.JRFillSubreport.loadReport(JRFillSubreport.java:411)
at net.sf.jasperreports.engine.fill.JRFillSubreport.evaluateReport(JRFillSubreport.java:378)
at net.sf.jasperreports.engine.fill.JRFillSubreport.evaluateSubreport(JRFillSubreport.java:440)
at net.sf.jasperreports.engine.fill.JRFillSubreport.evaluate(JRFillSubreport.java:354)
at net.sf.jasperreports.engine.fill.JRFillElementContainer.evaluate(JRFillElementContainer.java:383)
at net.sf.jasperreports.engine.fill.JRFillBand.evaluate(JRFillBand.java:506)
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillColumnBand(JRVerticalFiller.java:2412)
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillDetail(JRVerticalFiller.java:761)
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReportStart(JRVerticalFiller.java:240)
at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:99)
at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:607)
at net.sf.jasperreports.engine.fill.BaseReportFiller.fill(BaseReportFiller.java:387)
at net.sf.jasperreports.engine.fill.JRFiller.fill(JRFiller.java:109)
at net.sf.jasperreports.engine.JasperFillManager.fill(JasperFillManager.java:456)
at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:863)
at org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsView.doFillReport(AbstractJasperReportsView.java:702)
at org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsView.fillReport(AbstractJasperReportsView.java:669)
at org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsView.renderMergedOutputModel(AbstractJasperReportsView.java:566)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1271)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1037)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:980)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
... 97 common frames omitted

Note: running one single report without sub report works fine

Endpoint:

    @RequestMapping(value = "/api", params = {"dir", "name"})
public ModelAndView report(
    HttpServletRequest request,
    @RequestParam(value = "dir") String dir,
    @RequestParam(value = "name") String name) throws Exception {

    JasperReportsPdfView view = new JasperReportsPdfView();
    view.setJdbcDataSource(dataSource);
    view.setUrl("classpath:jasper/" + dir + "/" + name + ".jrxml");
    view.setApplicationContext(context);
    view.setHeaders(getProperties(name, false));
    return new ModelAndView(view, getParameters(request));
}

Java code crash RepositoryUtil:

    public <K extends Resource> K getResourceFromLocation(String location, Class<K> resourceType) throws JRException
{
    K resource = null;
    List<RepositoryService> services = getServices();
    if (services != null)
    {
        for (RepositoryService service : services)
        {
            resource = service.getResource(location, resourceType);
            if (resource != null)
            {
                break;
            }
        }
    }
    if (resource == null)
    {
        throw 
        new JRException(
            EXCEPTION_MESSAGE_KEY_RESOURCET_NOT_FOUND,
            new Object[]{location});    //FIXMEREPO decide whether to return null or throw exception; check everywhere
    }
    return resource;
}

Error line:

    resource = service.getResource(location, resourceType);

Variable:

  location -> jasper/repo/myreport_detail.jasper
  resourceType -> class net.sf.jasperreports.repo.ReportResource

Exception line thrown because resource = null:

    public static final String EXCEPTION_MESSAGE_KEY_RESOURCET_NOT_FOUND = "repo.resource.not.found";

Anyone?


Answer:

It seems as Jasper Studio problem that I had at 1 point. Try to check expression that you are using for sub-report in main report. Depending of version Jasper Report Studio expects sub-report to be defined as

"folderOfyourSubReport/myreport_detail.jrxml"

or

"folderOfyourSubReport/myreport_detail.jasper"