No estaba contento con la respuesta de lkdg porque quería separar la preocupación de cargar el archivo correcto del diseño ya que en mi opinión no debería forzarme a organizar desde dónde se cargan los informes en el momento del diseño del JRXML archivos.
Desafortunadamente, el código de la Biblioteca Jasper está lleno de referencias estáticas que dificultan encontrar el lugar correcto para la inyección de un subinportador personalizado y también algunas succiones (por ejemplo, la interfaz RepositoryService
carece completamente de contrato documentación por lo que necesita de adivinar el contrato mediante la lectura de código de llamada), pero es posible:
private static void fillReport() throws IOException, JRException {
// The master report can be loaded just like that, because the
// subreports will not be loaded at this point, but later when
// report is filled.
final JasperReport report = loadReport("masterReport.jasper");
// The SimpleJasperReportsContext allows us to easily specify some
// own extensions that can be injected into the fill manager. This
// class will also delegate to the DefaultJasperReportsContext and
// combine results. Thus all the default extensions will still be available
SimpleJasperReportsContext jasperReportsContext = new SimpleJasperReportsContext();
jasperReportsContext.setExtensions(
RepositoryService.class, singletonList(new SubReportFindingRepository())
);
final byte[] pdf = JasperExportManager.exportReportToPdf(
JasperFillManager
.getInstance(jasperReportsContext)
// carefully select the correct `fill` method here and don't
// accidentally select one of the static ones!:
.fill(report, YOUR_PARAMS, YOUR_CONNECTION)
);
}
private static JasperReport loadReport(final String fileName) throws IOException, JRException {
try(InputStream in = loadReportAsStream(fileName)) {
return (JasperReport) JRLoader.loadObject(in);
}
}
private static InputStream loadReportAsStream(final String fileName) {
final String resourceName = "/package/path/to/reports/" + fileName;
final InputStream report = CurrentClass.class.getResourceAsStream(resourceName);
if (report == null) {
throw new RuntimeException("Report not found: " + resourceName);
}
return report;
}
private static class SubReportFindingRepository implements RepositoryService {
@Override
public Resource getResource(final String uri) {
return null; // Means "not found". The next RepositoryService will be tried
}
@Override
public void saveResource(final String uri, final Resource resource) {
throw new UnsupportedOperationException();
}
@Override
public <K extends Resource> K getResource(final String uri, final Class<K> resourceType) {
if (!isKnownSubReport(uri)) {
return null; // Means "not found". The next RepositoryService will be tried
}
final ReportResource reportResource = new ReportResource();
try {
reportResource.setReport(loadReport(uri));
} catch (IOException | JRException e) {
throw new Error(e);
}
return resourceType.cast(reportResource);
}
private static boolean isKnownSubReport(final String uri) {
return "subReport1.jasper".equals(uri) || "subReport2.jasper".equals(uri);
}
}
Como alternativa a la inyección local también se puede escribir una global extension. Por lo que tengo (no lo intenté), esto requiere la creación de un archivo jasperreports_extension.properties
con nombres de clase que se deben cargar, que pueden incluir un repositorio personalizado para cargar los informes. Sin embargo, en este caso pierde completamente la capacidad de trabajar con configuraciones conflictivas necesarias en diferentes casos de uso.