Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jasper Reports: How to compile subreports

I have a standalone application, and one of its duties is to take the path of a *.jrxml file and compile it.

I can do this without problem until a report with a subreport comes up, where the compilation of the master does not compile any of its children, resulting in a subreport *.jasper file not found later down the track.

Is there any way to

1) Set the JasperCompileManager to automatically pick up subreports?

2) Get a list of paths to subreports contained within either a JasperDesign or JasperReport object?

I have no direct access to the jrxml files, so modifying the reports to suit the compile method is not an option, nor is applying any standard naming scheme to infer which subreports belong to which reports.

There is a similar problem here:

http://jasperforge.org/plugins/espforum/view.php?group_id=102&forumid=103&topicid=40683

where a JRVisitor is used to produce a list of JRSubreport objects, however there is no explanation of how to use this to get a path to the subreport in order to compile it and recursively look for subreports of subreports, and I cant figure it out.

like image 219
Numeron Avatar asked Apr 04 '12 04:04

Numeron


1 Answers

Ok, so it required a bit of hackery, but I was able to figure something out...

The subreport.getExpression().getText() returns the expression field of the subreport widget thing in the master report, and is a string that looks something like this

$P{SUBREPORT_DIR} + "/report_sub1.jasper"

So I was able to pull it apart to get the name using the following. Its not ideal, but it should hold up.

JRElementsVisitor.visitReport(jasperReport, new JRVisitor(){

  // ** snip other overrides **

  @Override
  public void visitSubreport(JRSubreport subreport){
    String expression = subreport.getExpression().getText().replace(".jasper", ".jrxml");
    StringTokenizer st = new StringTokenizer(expression, "\"/");
    String subreportName = null;
    while(st.hasMoreTokens())
      subreportName = st.nextToken();
    compileReport(subreportName);
  }
}

EDIT:

Here is my whole compileReport method, demonstrating how to recursively compile subreports of subreports etc. Not perfect, but good enough for my app. All compiled *.jasper files are saved back onto disk in the same location as the uncompiled *.jrxml files were picked up, but this wouldn't be hard to change. The compiled main report object is passed back incase you want to run it or whatever.

Remember that this code is 9 months old at the time of this edit, and newer versions of Jasper Reports may now have an inbuild functions for this kind of thing.

private static final String reportsPath = "someplace/nice/";
private ArrayList<String>   completedSubReports = new ArrayList<String>(30);
private Throwable           subReportException  = null;

/**
 * Recursively compile report and subreports
 */
public JasperReport compileReport(String reportName) throws Throwable{
  JasperDesign jasperDesign = JRXmlLoader.load(reportsPath + reportName + ".jrxml");
  JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign);
  JRSaver.saveObject(jasperReport, reportsPath + reportName + ".jasper");
  toLog("Saving compiled report to: " + reportsPath + reportName + ".jasper");
  //Compile sub reports
  JRElementsVisitor.visitReport(jasperReport, new JRVisitor(){
    @Override
    public void visitBreak(JRBreak breakElement){}

    @Override
    public void visitChart(JRChart chart){}

    @Override
    public void visitCrosstab(JRCrosstab crosstab){}

    @Override
    public void visitElementGroup(JRElementGroup elementGroup){}

    @Override
    public void visitEllipse(JREllipse ellipse){}

    @Override
    public void visitFrame(JRFrame frame){}

    @Override
    public void visitImage(JRImage image){}

    @Override
    public void visitLine(JRLine line){}

    @Override
    public void visitRectangle(JRRectangle rectangle){}

    @Override
    public void visitStaticText(JRStaticText staticText){}

    @Override
    public void visitSubreport(JRSubreport subreport){
      try{
        String expression = subreport.getExpression().getText().replace(".jasper", "");
        StringTokenizer st = new StringTokenizer(expression, "\"/");
        String subReportName = null;
        while(st.hasMoreTokens())
          subReportName = st.nextToken();
        //Sometimes the same subreport can be used multiple times, but
        //there is no need to compile multiple times
        if(completedSubReports.contains(subReportName)) return;
        completedSubReports.add(subReportName);
        compileReport(subReportName);
      }
      catch(Throwable e){
        subReportException = e;
      }
    }
    @Override
    public void visitTextField(JRTextField textField){}

    @Override
    public void visitComponentElement(JRComponentElement componentElement){}

    @Override
    public void visitGenericElement(JRGenericElement element){}
  });
  if(subReportException != null) throw new RuntimeException(subReportException);
  return jasperReport;
}
like image 100
Numeron Avatar answered Oct 02 '22 12:10

Numeron