Showing posts with label JasperReports. Show all posts
Showing posts with label JasperReports. Show all posts

Thursday, November 27, 2014

Jasper Reports, 1kb PDFs and "I swear they did this on purpose"

I ran into this seemingly weird situation with Jasper Reports 5.0.1 where I was getting 1kb size PDFs which was weird. So after doing *lots* (lots) of debugging I couldn't find anything wrong. The lines where pretty straight forward:

  InputStream is1 = evalSummaryReportPage1.getResource().openStream(); //jrxml source
  Map reportParams1 = new HashMap(); // standard hashmap
  reportParams1.put(......)
  ..
  ..
  JasperDesign page1Design = JRXmlLoader.load(is1);
  JasperReport page1Compiled = JasperCompileManager.compileReport(page1Design);
  JasperPrint page1 = JasperFillManager.fillReport(page1Compiled, reportParams1); 

  JRPdfExporter exporter = new JRPdfExporter();
  exporter.setParameter(JRExporterParameter.JASPER_PRINT, page1);
  exporter.setParameter(JRExporterParameter.OUTPUT_FILE_NAME, filename); 
  exporter.exportReport();

Until I found about JREmptyDataSource class. Apparently the fillReport() method of the JasperFillManager doesn't do shit when it doesn't has a third or the datasource parameter. It was bullshit because the same fillReport methods is overloaded and can be called with only two parameters. You can then compile with no problems or warnings. A warning would have been nice but no. I swear the Jasper devs did this on purpose just to fuck with me.

So to fix this shit:
  ..
  JasperDesign page1Design = JRXmlLoader.load(is1);
  JasperReport page1Compiled = JasperCompileManager.compileReport(page1Design);
  JasperPrint page1 = JasperFillManager.fillReport(page1Compiled, reportParams1, new JREmptyDataSource()); 
  ..
There, it now correctly generates the report.

zzzzzzzzz

Monday, March 18, 2013

Tapestry5 and JasperReports

Creating "reports" are a fact of life for developers. It's sucky work but necessary. With my Tapestry5 application, I decided to use JasperReports for the following reasons:
  1. It's free
  2. It's easy to use
  3. It has a nice plugin that works inside Netbeans: iReport
To get started, you have to add JasperReports dependency in your POM.xml:

 <dependency>  
   <groupId>net.sf.jasperreports</groupId>  
     <artifactId>jasperreports</artifactId>  
   <version>5.0.1</version>  
 </dependency>  
 <dependency>  
   <groupId>commons-collections</groupId>  
     <artifactId>commons-collections</artifactId>  
   <version>3.2.1</version>  
 </dependency>  

The commons-collections might be needed to fix a transitive dependency error that you might get with Tapestry5 and JasperReports.

The next part is the report template (jrxml) needs to be created. I just put it template in the WEB-INF folder so to access it is: /WEB-INF/reports/report1.jrxml

The report template is a rather rich XML file with a lot of moving parts but it shouldn't be that hard since we do have the iReport plugin to "visually" create the reports. Here is an example: test_jasper.jrxml. And here is a full working example.

The next piece of the puzzle is a custom Tapestry5 StreamResponse for PDF files. The idea is after we generate the report, we are going to send it to the client as a PDF file so they can print it.

 import java.io.IOException;  
 import java.io.InputStream;  
 import org.apache.tapestry5.StreamResponse;  
 import org.apache.tapestry5.services.Response;  
 /**  
  *  
  * @author jaypax  
  */  
 public class PDFStreamResponse implements StreamResponse {  
     private InputStream is;  
     private String filename="default";  
     /**  
      * Constructor  
      * @param is  
      * @param args (http://docs.oracle.com/javase/1.5.0/docs/guide/language/varargs.html  
      */  
     public PDFStreamResponse(InputStream is, String... args) {  
         this.is = is;  
         if (args != null) {  
             this.filename = args[0];  
         }  
     }  
     public String getContentType() {  
         return "application/pdf";  
     }  
     public InputStream getStream() throws IOException {  
         return is;  
     }  
     public void prepareResponse(Response arg0) {  
         arg0.setHeader("Content-Disposition", "attachment; filename="  
                 + filename + ".pdf");  
     }  
 }  

Then we can now add a event handler for that button when clicked sends the report. In my scenario, its inside a onSubmit event. This is on a page class that's populated with services injected via Tapestry's IOC. I also injected the report form as an asset.

    @Property
    private int id;

    @Inject
    @Path(value = "context:WEB-INF/reports/report1.jrxml")
    private Asset ReportForm;
    ....
    ....
    ....
    public StreamResponse onSubmitFromReportForm() throws IOException,JRException{
        InputStream is = ReportForm.getResource().openStream();
       
        JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(someDAO.findAllEntriesById(id));
        
        Map reportParams = new HashMap();
        reportParams.put("TITLE", "SOME REPORT TITLE");

        JasperDesign reportDesign = JRXmlLoader.load(is);
        JasperReport reportCompiled = JasperCompileManager.compileReport(reportDesign);
        JasperPrint reportPrinted = JasperFillManager.fillReport(reportCompiled, reportParams,ds);
           
        ByteArrayInputStream bais = new ByteArrayInputStream(JasperExportManager.exportReportToPdf(reportPrinted));
        
        return new PDFStreamResponse(bais, "Some Report");
    }

Notice the PDFStreamResponse as the return object, without that then browser will mishandle the return stream.

Turning this into a service shouldn't be that hard which I will probably do in a coming iteration.