From d58fb15f1892765d55486e41d6bef555a7f6acd6 Mon Sep 17 00:00:00 2001 From: hengsin Date: Wed, 8 Nov 2023 20:29:20 +0800 Subject: [PATCH] IDEMPIERE-5922 Jasper Report: support loading of jasper report file from non-fragment bundle (#2096) --- .../META-INF/MANIFEST.MF | 1 + .../report/jasper/BundleResourceLoader.java | 22 +- .../report/jasper/ClassResourceLoader.java | 24 +- org.idempiere.test/AR_Invoice_Bundle.jrxml | 867 ++++++++++++++++++ org.idempiere.test/build.properties | 3 +- .../test/jasper/PrintWithinProcess.java | 119 +++ 6 files changed, 1032 insertions(+), 4 deletions(-) create mode 100644 org.idempiere.test/AR_Invoice_Bundle.jrxml diff --git a/org.adempiere.report.jasper/META-INF/MANIFEST.MF b/org.adempiere.report.jasper/META-INF/MANIFEST.MF index 328d2846e2..dfa819bb3d 100644 --- a/org.adempiere.report.jasper/META-INF/MANIFEST.MF +++ b/org.adempiere.report.jasper/META-INF/MANIFEST.MF @@ -14,6 +14,7 @@ Import-Package: com.zaxxer.sparsebits;version="1.2.0", org.jfree.chart, org.jfree.chart.plot, org.osgi.framework;version="1.10.0", + org.osgi.framework.wiring;version="1.2.0", org.osgi.service.event Require-Bundle: org.adempiere.base;bundle-version="0.0.0", net.sf.jasperreports.engine;bundle-version="6.3.1", diff --git a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/BundleResourceLoader.java b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/BundleResourceLoader.java index f0970060a6..7b47bfac26 100644 --- a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/BundleResourceLoader.java +++ b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/BundleResourceLoader.java @@ -33,12 +33,15 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.Arrays; import java.util.Enumeration; +import java.util.Optional; import java.util.logging.Level; import org.compiere.util.CLogger; import org.compiere.util.Language; import org.compiere.utils.DigestOfFile; +import org.osgi.framework.Bundle; /** * @@ -75,12 +78,29 @@ public class BundleResourceLoader { boolean empty = true; String parentPath = "/"; String bundleFileName = resourceName; + String bundleName = null; + //check is resource name include optional bundle symbolic name (syntax -> bundle:bundleSymbolicName:jasperReportFileName) + if (resourceName.indexOf(":") > 0) { + bundleName = resourceName.substring(0, resourceName.indexOf(":")); + resourceName = resourceName.substring(resourceName.indexOf(":")+1); + bundleFileName = resourceName; + } if (resourceName.lastIndexOf("/") > 0) { parentPath = "/"+resourceName.substring(0, resourceName.lastIndexOf("/")); bundleFileName = resourceName.substring(resourceName.lastIndexOf("/")+1); } URL url = null; - Enumeration entries = Activator.getBundleContext().getBundle().findEntries(parentPath, bundleFileName, false); + Bundle bundle = null; + //if resource name include optional bundle symbolic name, load from specific bundle + if (bundleName == null) { + bundle = Activator.getBundleContext().getBundle(); + } else { + String symbolicName = bundleName; + Optional optional = Arrays.stream(Activator.getBundleContext().getBundles()).filter(b -> b.getSymbolicName().equals(symbolicName)).findFirst(); + if (optional.isPresent()) + bundle = optional.get(); + } + Enumeration entries = bundle != null ? bundle.findEntries(parentPath, bundleFileName, false) : null; if (entries != null && entries.hasMoreElements()) url = entries.nextElement(); if (url != null) { diff --git a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ClassResourceLoader.java b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ClassResourceLoader.java index 30c5cc1f24..17db9f832d 100644 --- a/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ClassResourceLoader.java +++ b/org.adempiere.report.jasper/src/org/adempiere/report/jasper/ClassResourceLoader.java @@ -33,11 +33,15 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Optional; import java.util.logging.Level; import org.compiere.util.CLogger; import org.compiere.util.Language; import org.compiere.utils.DigestOfFile; +import org.osgi.framework.Bundle; +import org.osgi.framework.wiring.BundleWiring; /** * @@ -72,6 +76,22 @@ public class ClassResourceLoader { URL url = null; File reportFile = null; String resourceName = path.startsWith(RESOURCE_PATH_PREFIX) ? path.substring(RESOURCE_PATH_PREFIX.length()).trim() : path.trim(); + Bundle bundle = null; + //if resource name include optional bundle symbolic name (syntax -> resource:bundleSymbolicName:jasperReportFileName), find the specific bundle + if (resourceName.indexOf(":") > 0) { + String symbolicName = resourceName.substring(0, resourceName.indexOf(":")); + resourceName = resourceName.substring(resourceName.indexOf(":")+1); + Optional optional = Arrays.stream(Activator.getBundleContext().getBundles()).filter(b -> b.getSymbolicName().equals(symbolicName)).findFirst(); + if (optional.isPresent()) + bundle = optional.get(); + } + ClassLoader classLoader = null; + //if resource name include optional bundle symbolic name, use classLoader of the specific bundle + if (bundle != null) { + classLoader = bundle.adapt(BundleWiring.class).getClassLoader(); + } else { + classLoader = getClass().getClassLoader(); + } //only copy to tmp if reportPath is jrxml file (for compilation) if (resourceName.endsWith(".jrxml")) { String localResourceName = toLocalName(resourceName); @@ -89,7 +109,7 @@ public class ClassResourceLoader { String extension = localFileName.substring(localFileName.lastIndexOf(".")); File tmpOutputFile = null; - url = getClass().getClassLoader().getResource(resourceName); + url = classLoader.getResource(resourceName); if (url != null) { try (InputStream inputStream = url.openStream()) { if (inputStream != null) { @@ -131,7 +151,7 @@ public class ClassResourceLoader { } } } else { - url = getClass().getClassLoader().getResource(resourceName); + url = classLoader.getResource(resourceName); empty = (url == null); } diff --git a/org.idempiere.test/AR_Invoice_Bundle.jrxml b/org.idempiere.test/AR_Invoice_Bundle.jrxml new file mode 100644 index 0000000000..175623e2b3 --- /dev/null +++ b/org.idempiere.test/AR_Invoice_Bundle.jrxml @@ -0,0 +1,867 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "+ $F{description})]]> + + + + + + + + + + + + CUSTOMER: " + $F{custnum}) + +($F{documentno} == null ? "" : "
INVOICE: " + $F{documentno}) + +($F{dateinvoiced} == null ? "" : "
DATE: " + DATEFORMAT($F{dateinvoiced},"dd-MM-yyyy"))]]>
+
+ + + + + + + + + + " + $F{custname2})]]> + + + + + + + + + + + + + " + $F{address2}) + +($F{address3} == null ? "" : "
" + $F{address3}) + +($F{address4} == null ? "" : "
" + $F{address4})]]>
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/org.idempiere.test/build.properties b/org.idempiere.test/build.properties index 1c732b078c..eae3d09094 100644 --- a/org.idempiere.test/build.properties +++ b/org.idempiere.test/build.properties @@ -2,5 +2,6 @@ source.. = src/ output.. = target/classes/ bin.includes = META-INF/,\ .,\ - OSGI-INF/ + OSGI-INF/,\ + AR_Invoice_Bundle.jrxml jre.compilation.profile = JavaSE-17 diff --git a/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java b/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java index a28b8b9e7a..8bcf2fd5cc 100644 --- a/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java +++ b/org.idempiere.test/src/org/idempiere/test/jasper/PrintWithinProcess.java @@ -145,4 +145,123 @@ public class PrintWithinProcess extends AbstractTestCase { throw new AdempiereException("Resource " + resource + " not found"); } + @Test + public void testPrintWithBundleResource() { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + MProcess process = null; + try { + process = new MProcess(ctx, 0, trxName); + process.set_ValueNoCheck("AD_Client_ID", 0); + process.setAD_Org_ID(0); + process.setJasperReport("bundle:org.idempiere.test:/AR_Invoice_Bundle.jrxml"); + process.setName("Test Invoice Jasper"); + process.setValue("Test_Invoice_Jasper"); + process.saveCrossTenantSafeEx(); + commit(); + + List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) + .setClient_ID() + .setOnlyActiveRecords(true) + .setParameters(103, 109) + .list(); + for (MInvoice invoice : invoices) { + invoice.setDescription("Test Printing within a Process"); + invoice.saveEx(); + } + + ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); + pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); + pi.setAD_User_ID(getAD_User_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setPrintPreview(false); + pi.setIsBatch(true); + Trx trx = Trx.get(trxName, false); + + List pdfList = new ArrayList(); + for (MInvoice invoice : invoices) { + pi.setRecord_ID(invoice.getC_Invoice_ID()); + ProcessUtil.startJavaProcess(Env.getCtx(), pi, trx, false); + assertFalse(pi.isError(), pi.getSummary()); + assertFalse(pi.getPDFReport() == null); + pdfList.add(pi.getPDFReport()); + } + assertFalse(pdfList.isEmpty()); + } finally { + rollback(); + if (process != null) { + int oldRole = Env.getAD_Role_ID(ctx); + try { + PO.setCrossTenantSafe(); + Env.setContext(ctx, Env.AD_ROLE_ID, 0); // to allow deleting process + process.deleteEx(true); + } finally { + Env.setContext(ctx, Env.AD_ROLE_ID, oldRole); + PO.clearCrossTenantSafe(); + } + } + commit(); + } + } + + @Test + public void testPrintWithClassPathResource() { + Properties ctx = Env.getCtx(); + String trxName = getTrxName(); + + MProcess process = null; + try { + process = new MProcess(ctx, 0, trxName); + process.set_ValueNoCheck("AD_Client_ID", 0); + process.setAD_Org_ID(0); + process.setJasperReport("resource:org.idempiere.test:org/idempiere/test/jasper/AR_Invoice.jrxml"); + process.setName("Test Invoice Jasper"); + process.setValue("Test_Invoice_Jasper"); + process.saveCrossTenantSafeEx(); + commit(); + + List invoices = new Query(ctx, MInvoice.Table_Name, "C_Invoice_ID IN (?,?)", trxName) + .setClient_ID() + .setOnlyActiveRecords(true) + .setParameters(103, 109) + .list(); + for (MInvoice invoice : invoices) { + invoice.setDescription("Test Printing within a Process"); + invoice.saveEx(); + } + + ProcessInfo pi = new ProcessInfo (process.getName(), process.getAD_Process_ID()); + pi.setClassName(ProcessUtil.JASPER_STARTER_CLASS); + pi.setAD_User_ID(getAD_User_ID()); + pi.setAD_Client_ID(getAD_Client_ID()); + pi.setPrintPreview(false); + pi.setIsBatch(true); + Trx trx = Trx.get(trxName, false); + + List pdfList = new ArrayList(); + for (MInvoice invoice : invoices) { + pi.setRecord_ID(invoice.getC_Invoice_ID()); + ProcessUtil.startJavaProcess(Env.getCtx(), pi, trx, false); + assertFalse(pi.isError(), pi.getSummary()); + assertFalse(pi.getPDFReport() == null); + pdfList.add(pi.getPDFReport()); + } + assertFalse(pdfList.isEmpty()); + } finally { + rollback(); + if (process != null) { + int oldRole = Env.getAD_Role_ID(ctx); + try { + PO.setCrossTenantSafe(); + Env.setContext(ctx, Env.AD_ROLE_ID, 0); // to allow deleting process + process.deleteEx(true); + } finally { + Env.setContext(ctx, Env.AD_ROLE_ID, oldRole); + PO.clearCrossTenantSafe(); + } + } + commit(); + } + } }