Skip to content

Commit

Permalink
testing image inclusion in reports
Browse files Browse the repository at this point in the history
* set up a report that includes images from inside the application, from the static resources of the application, and from an external source
* added a unit test to validate that the HTML export includes `img` tags with not-empty `src` attributes
* added endpoints for PDF and XLSX output
* added an application-wide HTML file resource handler to deal with storing the images in a temp folder
* setup and tear down the temp folder and any files in it with the application lifecycle
* added a REST endpoint to serve images from the temp directory for HTML reports

Signed-off-by:Nathan Erwin <[email protected]>
  • Loading branch information
nderwin committed Oct 18, 2024
1 parent 729d15c commit 58aa15d
Show file tree
Hide file tree
Showing 11 changed files with 378 additions and 24 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
ObjectStore
/integration-tests/nbproject/
31 changes: 31 additions & 0 deletions integration-tests/src/main/jasperreports/ImagesReport.jrxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!-- Created with Jaspersoft Studio version 7.0.0.final using JasperReports Library version 7.0.0 -->
<jasperReport name="ImagesReport" language="java" pageWidth="612" pageHeight="792" whenNoDataType="AllSectionsNoDetail" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="7ed005ff-df0a-4cda-8daa-ffa0721c8e4f">
<parameter name="QUARKUS_URL" class="java.lang.String">
<defaultValueExpression><![CDATA["http://localhost:8080"]]></defaultValueExpression>
</parameter>
<title height="530" splitType="Stretch">
<element kind="staticText" uuid="465ce796-bd9a-432d-b0fe-70672e692e72" x="0" y="0" width="572" height="30" hTextAlign="Center" vTextAlign="Middle">
<text><![CDATA[Images]]></text>
</element>
<element kind="staticText" uuid="fa677fd0-4085-4b99-9dab-2262569dfb15" x="0" y="40" width="280" height="50" hTextAlign="Center" vTextAlign="Middle">
<text><![CDATA[Relative to report]]></text>
</element>
<element kind="image" uuid="63ad371f-62a0-4324-bae6-4088e6b359bd" x="280" y="40" width="50" height="50" hImageAlign="Center" vImageAlign="Middle">
<expression><![CDATA["images/jasperreports.svg"]]></expression>
</element>
<element kind="staticText" uuid="ae851b5b-8475-43bb-9d82-9e2d020c7d41" x="0" y="100" width="280" height="50" hTextAlign="Center" vTextAlign="Middle">
<text><![CDATA[URL to localhost, gif]]></text>
</element>
<element kind="image" uuid="72c0a16f-5b16-4a1d-9132-00387f80b179" x="280" y="100" width="50" height="50" hImageAlign="Center" vImageAlign="Middle">
<expression><![CDATA[$P{QUARKUS_URL} + "/jasperreports.gif"]]></expression>
</element>
<element kind="staticText" uuid="f8eb2a65-ed08-4dd4-9d93-da923703a386" x="0" y="160" width="280" height="50" hTextAlign="Center" vTextAlign="Middle">
<text><![CDATA[URL to remote server]]></text>
</element>
<element kind="image" uuid="183403c6-1aef-410a-b6f1-5767241f28bf" x="280" y="160" width="50" height="50" linkType="Reference" linkTarget="Blank" hImageAlign="Center" vImageAlign="Middle">
<expression><![CDATA["https://raw.githubusercontent.com/quarkiverse/quarkus-jasperreports/refs/heads/main/docs/modules/ROOT/assets/images/jasperreports.svg"]]></expression>
<hyperlinkReferenceExpression><![CDATA["https://raw.githubusercontent.com/quarkiverse/quarkus-jasperreports/refs/heads/main/docs/modules/ROOT/assets/images/jasperreports.svg"]]></hyperlinkReferenceExpression>
<hyperlinkTooltipExpression><![CDATA["Image from GitHub"]]></hyperlinkTooltipExpression>
</element>
</title>
</jasperReport>
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
package io.quarkiverse.jasperreports.it;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import jakarta.inject.Inject;

import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.ReportContext;
import net.sf.jasperreports.engine.export.HtmlExporter;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.JRRtfExporter;
import net.sf.jasperreports.engine.export.JRXmlExporter;
import net.sf.jasperreports.engine.export.oasis.JROdsExporter;
import net.sf.jasperreports.engine.export.oasis.JROdtExporter;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import net.sf.jasperreports.export.SimpleExporterInput;
import net.sf.jasperreports.export.SimpleHtmlExporterOutput;
import net.sf.jasperreports.export.SimpleOdsReportConfiguration;
import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput;
import net.sf.jasperreports.export.SimpleWriterExporterOutput;
import net.sf.jasperreports.export.SimpleXlsxReportConfiguration;
import net.sf.jasperreports.export.SimpleXmlExporterOutput;
import net.sf.jasperreports.pdf.JRPdfExporter;

public abstract class AbstractJasperResource {

@Inject
Application app;

protected ByteArrayOutputStream exportCsv(JasperPrint jasperPrint) throws JRException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
JRCsvExporter exporter = new JRCsvExporter();
Expand All @@ -45,12 +54,18 @@ protected ByteArrayOutputStream exportXml(JasperPrint jasperPrint, boolean embed
return outputStream;
}

protected ByteArrayOutputStream exportHtml(JasperPrint jasperPrint) throws JRException {
protected ByteArrayOutputStream exportHtml(JasperPrint jasperPrint, ReportContext reportContext)
throws JRException, IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HtmlExporter exporter = new HtmlExporter(DefaultJasperReportsContext.getInstance());

exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
exporter.setExporterOutput(new SimpleHtmlExporterOutput(outputStream));

SimpleHtmlExporterOutput htmlExporter = new SimpleHtmlExporterOutput(outputStream);
htmlExporter.setImageHandler(app.getImageHandler());

exporter.setExporterOutput(htmlExporter);
exporter.setReportContext(reportContext);

exporter.exportReport();
return outputStream;
Expand Down Expand Up @@ -104,4 +119,20 @@ protected ByteArrayOutputStream exportPdf(JasperPrint jasperPrint) throws JRExce
return outputStream;
}

}
protected ByteArrayOutputStream exportXlsx(JasperPrint jasperPrint) throws JRException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

JRXlsxExporter exporter = new JRXlsxExporter();

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

SimpleXlsxReportConfiguration configuration = new SimpleXlsxReportConfiguration();
configuration.setOnePagePerSheet(true);
exporter.setConfiguration(configuration);

exporter.exportReport();
return outputStream;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.quarkiverse.jasperreports.it;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;

import org.jboss.logging.Logger;

import io.quarkus.runtime.ShutdownEvent;
import io.quarkus.runtime.StartupEvent;
import net.sf.jasperreports.engine.export.FileHtmlResourceHandler;

@ApplicationScoped
public class Application {

private static final Logger LOG = Logger.getLogger(Application.class);

private FileHtmlResourceHandler imageHandler;

private Path tempFolder;

void onStart(@Observes StartupEvent evt) throws IOException {
tempFolder = Files.createTempDirectory("jasperreports");
imageHandler = new FileHtmlResourceHandler(tempFolder.toFile(), "/images/{0}");
}

void onStop(@Observes ShutdownEvent evt) throws IOException {
if (null != tempFolder) {
Files.walk(tempFolder).forEach((p) -> {
try {
Files.delete(p);
} catch (IOException ex) {
LOG.warn(ex.getMessage());
}
});

if (Files.exists(tempFolder)) {
Files.delete(tempFolder);
}
}
}

public FileHtmlResourceHandler getImageHandler() {
return imageHandler;
}

public Path getTempFolder() {
return tempFolder;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkiverse.jasperreports.it;

import static jakarta.ws.rs.core.Response.Status.NOT_FOUND;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Response;

import org.jboss.logging.Logger;

@Path("images")
@RequestScoped
public class ImageResource {

private static final Logger LOG = Logger.getLogger(ImageResource.class);

@Inject
Application app;

@GET
@Path("{fileName}")
public Response get(@PathParam("fileName") final String fileName) throws IOException {
final var path = java.nio.file.Path.of(app.getTempFolder().toString(), fileName);

if (Files.exists(path) && Files.isRegularFile(path) && Files.isReadable(path)) {
final String contentType = Files.probeContentType(path);

LOG.debugf("Loading file %s of type %s", path.toString(), contentType);

try (final InputStream is = Files.newInputStream(path)) {
return Response.ok(is.readAllBytes(), contentType).build();
}
}

return Response.status(NOT_FOUND).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package io.quarkiverse.jasperreports.it;

import static io.quarkiverse.jasperreports.it.ExtendedMediaType.APPLICATION_PDF;
import static io.quarkiverse.jasperreports.it.ExtendedMediaType.APPLICATION_XLSX;
import static jakarta.ws.rs.core.MediaType.TEXT_HTML;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;

import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;

import io.quarkiverse.jasperreports.repository.ReadOnlyStreamingService;
import io.quarkus.logging.Log;
import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.SimpleReportContext;

@Path("jasper/image")
@ApplicationScoped
public class JasperReportsImageResource extends AbstractJasperResource {

private static final String TEST_REPORT_NAME = "ImagesReport.jasper";

@Context
UriInfo uriInfo;

@Inject
ReadOnlyStreamingService repo;

@APIResponse(responseCode = "200", description = "Fetch an HTML report with images", content = @Content(mediaType = TEXT_HTML))
@GET
@Path("html")
public Response getHtml() throws JRException, IOException {
final SimpleReportContext reportContext = new SimpleReportContext();
final Map<String, Object> params = new HashMap<>();
params.put(JRParameter.REPORT_LOCALE, Locale.US);
params.put(JRParameter.REPORT_CONTEXT, reportContext);
params.put("QUARKUS_URL", uriInfo.getBaseUri().toString());

final long start = System.currentTimeMillis();
final JasperPrint jasperPrint = JasperFillManager.getInstance(repo.getContext()).fillFromRepo(TEST_REPORT_NAME, params,
new JREmptyDataSource());

final Response.ResponseBuilder response = Response.ok();
ByteArrayOutputStream outputStream = exportHtml(jasperPrint, reportContext);
Log.infof("HTML creation time : %s", (System.currentTimeMillis() - start));
response.entity(outputStream.toByteArray());
response.type(TEXT_HTML);

return response.build();
}

@APIResponse(responseCode = "200", description = "Fetch a PDF report with images", content = @Content(mediaType = APPLICATION_PDF))
@GET
@Path("pdf")
public Response getPdf() throws JRException {
final SimpleReportContext reportContext = new SimpleReportContext();
final Map<String, Object> params = new HashMap<>();
params.put(JRParameter.REPORT_LOCALE, Locale.US);
params.put(JRParameter.REPORT_CONTEXT, reportContext);
params.put("QUARKUS_URL", uriInfo.getBaseUri().toString());

final long start = System.currentTimeMillis();
final JasperPrint jasperPrint = JasperFillManager.getInstance(repo.getContext()).fillFromRepo(TEST_REPORT_NAME, params,
new JREmptyDataSource());

final Response.ResponseBuilder response = Response.ok();
ByteArrayOutputStream outputStream = exportPdf(jasperPrint);
Log.infof("PDF creation time : %s", (System.currentTimeMillis() - start));
response.entity(outputStream.toByteArray());
response.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=jasper.pdf");
response.type(APPLICATION_PDF);

return response.build();
}

@APIResponse(responseCode = "200", description = "Fetch an XLSX report with images", content = @Content(mediaType = APPLICATION_XLSX))
@GET
@Path("xlsx")
public Response getXlsx() throws JRException {
final SimpleReportContext reportContext = new SimpleReportContext();
final Map<String, Object> params = new HashMap<>();
params.put(JRParameter.REPORT_LOCALE, Locale.US);
params.put(JRParameter.REPORT_CONTEXT, reportContext);
params.put("QUARKUS_URL", uriInfo.getBaseUri().toString());

final long start = System.currentTimeMillis();
final JasperPrint jasperPrint = JasperFillManager.getInstance(repo.getContext()).fillFromRepo(TEST_REPORT_NAME, params,
new JREmptyDataSource());

final Response.ResponseBuilder response = Response.ok();
ByteArrayOutputStream outputStream = exportXlsx(jasperPrint);
Log.infof("XLSX creation time : %s", (System.currentTimeMillis() - start));
response.entity(outputStream.toByteArray());
response.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=jasper.xlsx");
response.type(APPLICATION_XLSX);

return response.build();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import static jakarta.ws.rs.core.MediaType.WILDCARD;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -82,16 +83,17 @@ public class JasperReportsStyleResource extends AbstractJasperResource {
})
@GET
public Response get(@Context HttpHeaders headers,
@DefaultValue("false") @QueryParam("embedded") boolean embedded) throws JRException {
@DefaultValue("false") @QueryParam("embedded") boolean embedded) throws JRException, IOException {

final SimpleReportContext reportContext = new SimpleReportContext();
final Map<String, Object> params = new HashMap<>();
Document document = JRXmlUtils.parse(JRLoader.getLocationInputStream("data/northwind.xml"));
params.put(JRXPathQueryExecuterFactory.PARAMETER_XML_DATA_DOCUMENT, document);
params.put(JRXPathQueryExecuterFactory.XML_DATE_PATTERN, "yyyy-MM-dd");
params.put(JRXPathQueryExecuterFactory.XML_NUMBER_PATTERN, "#,##0.##");
params.put(JRXPathQueryExecuterFactory.XML_LOCALE, Locale.ENGLISH);
params.put(JRParameter.REPORT_LOCALE, Locale.US);
params.put(JRParameter.REPORT_CONTEXT, new SimpleReportContext());
params.put(JRParameter.REPORT_CONTEXT, reportContext);

final long start = System.currentTimeMillis();
final JasperPrint jasperPrint = JasperFillManager.getInstance(repo.getContext()).fillFromRepo(TEST_REPORT_NAME, params);
Expand All @@ -114,7 +116,7 @@ public Response get(@Context HttpHeaders headers,
response.type(MediaType.APPLICATION_XML);
}
case TEXT_HTML -> {
ByteArrayOutputStream outputStream = exportHtml(jasperPrint);
ByteArrayOutputStream outputStream = exportHtml(jasperPrint, reportContext);
Log.infof("HTML creation time : %s", (System.currentTimeMillis() - start));
response.entity(outputStream.toByteArray());
response.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=jasper.html");
Expand Down
Loading

0 comments on commit 58aa15d

Please sign in to comment.