Passing a list of arguments to a Jasper Reports query

Asked

Viewed 1,086 times

6

I’m redoing some reports on Jasper Reports here at the company. I am migrating to have a reporting service agnostic the SQL query structure of the report. Before, the system knew deeply how such a query was made, but this ran into some limitations that I have.

I need to make an appointment with a clause IN; for example, knowing the orders going to the US, Italy and Germany.

A case similar to the one TIBCO itself put in documentation of parameters in queries

Then my consultation would be this:

SELECT * FROM ORDERS WHERE SHIPCOUNTRY IN ('USA','Italy','Germany')

In JRXML, I had the query this way:

<parameter name="P_WHERE_CLAUSE" class="java.lang.String"/>
<queryString>
    <![CDATA[
        SELECT * FROM ORDERS $P!{P_WHERE_CLAUSE}
    ]]>
</queryString>

And in the call to report, the old Java code was like this:

// public class RelatorioDownloadServlet extends HttpServlet {...
// ...
// protected void doPost(HttpServletRequest request,
//    HttpServletResponse response) throws ServletException, IOException { ...

String whereClause = " ";

String shippingCountries = request.getParameter("shipping");
if (shippingCountries  != null && !shippingCountries.isEmpty()) {
  whereClause = " WHERE SHIPCOUNTRY IN (" + shippingCountries + ")";
}

HashMap<String, Object> parametros = new HashMap<String, Object>();
parametros.put("P_WHERE_CLAUSE", whereClause );
String templatePath = servletContext.getRealPath("shipping.jasper");
byte[] bytes;
try (Connection connection = getConnection()) {
  bytes = JasperRunManager.runReportToPdf(templatePath, parametros, connection);
}
ServletOutputStream servletOutputStream = response.getOutputStream();
response.setContentType("application/pdf");
response.setContentLength(bytes.length);
servletOutputStream.write(bytes, 0, bytes.length);
servletOutputStream.flush();

Only that model doesn’t satisfy me. I build the (partially) external query to JRXML itself and the execution of this query successfully depends on the caller knowing the report in depth a priori.

As previously said, I am switching to a generic service of receiving the parameters of an abstract caller and then assembling the report. I’m doing this job in Springboot, for other reports he does the following:

@RestController
public class RelatorioController {

  @Autowired HikariDataSource dataSource;
  /**
   * CompileJasperRecursive é uma classe minha, ela possui dois métodos principais:
   * - getJasperReport: retorna um objeto JasperReport baseado no .jasper; não existindo o .jasper, compila-o chamando !compileIfShould"
   * - compileIfShould: verifica se deve gerar um novo .jasper baseado no .jrxml; também tenta procurar recursivamente por subrelatórios e compilá-los com "compileIfShould"
   *
   * não vejo esse cara como o problema, ele só faz isso
   */
  @Autowired CompileJasperRecursive compiler;

  @RequestMapping(path = "/{relat}.pdf", method = RequestMethod.POST, produces = "application/pdf")
  public void requisicaoPdf(@PathVariable("relat") String relat,
           @RequestParam Map<String, String> params,
           HttpServletRequest req, HttpServletResponse resp) throws JRException, SQLException, IOException {
    compilaRelatPDF(relat, params, resp.getOutputStream());
    resp.setHeader("Content-Disposition", "attachment; filename=" + relat + ".pdf");
  }

  private void compilaRelatPDF(String relat, Map<String, String> params, OutputStream outputStream) throws JRException, SQLException, IOException {
    JasperReport jasperReport = compiler.getJasperReport("/path/relatorios/", relat + ".jrxml");
    HashMap<String, Object> map = new HashMap<>();
    map.putAll(params);
    map.put("P_CAMINHO_SUB_RELAT", "/path/relatorios/"); // ocasional caminho dos subrelatórios
    
    JasperPrint print = fillReport(jasperReport, map);
    JRPdfExporter exporter = new JRPdfExporter();

    exporter.setExporterInput(new SimpleExporterInput(print));
    exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outputStream));

    SimplePdfReportConfiguration reportConfig = new SimplePdfReportConfiguration();
    reportConfig.setSizePageToContent(true);
    reportConfig.setForceLineBreakPolicy(false);

    SimplePdfExporterConfiguration exportConfig = new SimplePdfExporterConfiguration();
    exportConfig.setMetadataAuthor("Jeff Coelho Quesado");
    exportConfig.setEncrypted(false);
    exportConfig.setAllowedPermissionsHint("PRINTING");

    exporter.setConfiguration(reportConfig);
    exporter.setConfiguration(exportConfig);
    exporter.exportReport();
  }
}

In this scheme, without worrying where the request comes from nor the parameters a priori, I can assemble all reports that don’t need lists (passing the parameters as String). But I’m not at peace yet on the part of sending those lists.

Looking at the documentation and that issue here at Sopt, apparently the expansion $X would do what I need. My consultation would look like this:

SELECT * FROM ORDERS WHERE $X{IN, SHIPCOUNTRY, myCountries}

But I don’t know how to declare the parameters in JRXML. In this case, it would look something like this?

<parameter name="myCountries" class="java.util.Collection"/>
<queryString>
    <![CDATA[
        SELECT * FROM ORDERS WHERE $X{IN, SHIPCOUNTRY, myCountries}
    ]]>
</queryString>

And how would I handle the data of my request parameter? I would like to treat the receipt of application/x-www-form-urlencoded to avoid any problems with SOP, but I have not yet tested pass vectors through this format.

1 answer

2


The part of .jrxml is correct. The use of expansion $X{} was done in an appropriate way. But the receipt of the form was not done in an appropriate manner.

In the javadoc of @RequestParam has the following:

If the method Parameter is Map<String, String> or MultiValueMap<String, String> and a Parameter name is not specified, then the map Parameter is populated with all request Parameter Names and values.

In free translation:

If the method parameter is Map<String, String> or MultiValueMap<String, String> and a parameter name is not specified, so the map parameter will be populated with all and values of the request parameters.

When trying to use the Map, Spring Boot interprets it as a normal map and thus will only have a single value for the key. It is as if the request arrived

myCountries=USA
myCountries=Italy
myCountries=Germany

And this is what Spring would do:

Map<String, String> params = new HashMap<>();
params.put("myCountries", "USA");
params.put("myCountries", "Italy");
params.put("myCountries", "Germany");

So by doing params.get("myCountries") I get as an answer "Germany", thus failing to adequately pass the parameters of the consultation.

When using the MultiValueMap<String, String>, we are working with a multimope, that in the specific case is a specialization of the interface Map<String,List<String>>. So if I were to relay the arguments directly to Jasper to work, I would have to adapt all my report parameters to be java.util.List, even if I make sure it contains at most a single string.

So, how do I treat it? I made a convention that if my argument is multivariate, then it should end with []. In HTML, it would be something like this:

<input type="text" name="myCountries[]" placeholder="primeiro país"></input>
<input type="text" name="myCountries[]" placeholder="segundo país"></input>

Then the values would be added Verbatim in the Jasper parameters by removing the brackets from the parameter name. The other values would be added only the first element:

params.forEach((k, v) -> {
  if (k.endsWith("[]")) {
    map.put(k.substring(0, k.length() - 2), v);
  } else {
    map.put(k, v.get(0));
  }
});

The idea of making this standardization of names came after reading that article; despite being in PHP, I ended up using this standardization at the end. It also comes in handy that many of the articles that appear on Google about sending vectors through HTML forms are in PHP, so it will be less work for the team to try to internalize it.

No special treatment was done to pass the arguments as positional vectors (i.e., in the form elements ter name="myCountries[1]", name="myCountries[2]" etc) and associations (if used name="form[address]" or name="form[user]"). So although it’s a solution that deals with most of the cases I come across in the company, it’s not yet the overall solution.

Summary

In the .jrxml:

<parameter name="myCountries" class="java.util.Collection"/>
<queryString>
    <![CDATA[
        SELECT * FROM ORDERS WHERE $X{IN, SHIPCOUNTRY, myCountries}
    ]]>
</queryString>

In the form:

<input type="text" name="myCountries[]" placeholder="parâmtro multivalorado"></input>
<input type="text" name="myCountries[]" placeholder="parâmtro multivalorado"></input>
<input type="text" name="otherParam" placeholder="parâmtro escalar"></input>

On the server side:

@RestController
public class RelatorioController {

  @Autowired HikariDataSource dataSource;
  @Autowired CompileJasperRecursive compiler;

  @RequestMapping(path = "/{relat}.pdf", method = RequestMethod.POST, produces = "application/pdf")
  public void requisicaoPdf(@PathVariable("relat") String relat,
           @RequestParam MultiValueMap<String, String> params params,
           HttpServletRequest req, HttpServletResponse resp) throws JRException, SQLException, IOException {
    compilaRelatPDF(relat, params, resp.getOutputStream());
    resp.setHeader("Content-Disposition", "attachment; filename=" + relat + ".pdf");
  }

  private void compilaRelatPDF(String relat, MultiValueMap<String, String> params, OutputStream outputStream) throws JRException, SQLException, IOException {
    JasperReport jasperReport = compiler.getJasperReport("/path/relatorios/", relat + ".jrxml");
    HashMap<String, Object> map = new HashMap<>();

    params.forEach((k, v) -> {
      if (k.endsWith("[]")) {
        map.put(k.substring(0, k.length() - 2), v);
      } else {
        map.put(k, v.get(0));
      }
    });
    map.put("P_CAMINHO_SUB_RELAT", "/path/relatorios/"); // ocasional caminho dos subrelatórios

    JasperPrint print = fillReport(jasperReport, map);
    JRPdfExporter exporter = new JRPdfExporter();

    exporter.setExporterInput(new SimpleExporterInput(print));
    exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(outputStream));

    SimplePdfReportConfiguration reportConfig = new SimplePdfReportConfiguration();
    reportConfig.setSizePageToContent(true);
    reportConfig.setForceLineBreakPolicy(false);

    SimplePdfExporterConfiguration exportConfig = new SimplePdfExporterConfiguration();
    exportConfig.setMetadataAuthor("Jeff Coelho Quesado");
    exportConfig.setEncrypted(false);
    exportConfig.setAllowedPermissionsHint("PRINTING");

    exporter.setConfiguration(reportConfig);
    exporter.setConfiguration(exportConfig);
    exporter.exportReport();
  }
  • Only vectors of scalar values
  • Not treated to send a value in specific position
  • Was not treated vector associative

Browser other questions tagged

You are not signed in. Login or sign up in order to post.