From aedcde71bcdb84390c669dbb714f34f000e1b1de Mon Sep 17 00:00:00 2001 From: melloware Date: Wed, 9 Oct 2024 10:08:36 -0400 Subject: [PATCH] Native work in progress --- deployment/pom.xml | 2 +- .../deployment/BatikProcessor.java | 86 ++++ .../deployment/JasperReportsProcessor.java | 396 +++++++++++++++--- .../deployment/OpenPdfProcessor.java | 90 ++++ .../deployment/SaxonProcessor.java | 38 ++ .../deployment/XalanProcessor.java | 78 ++++ docs/modules/ROOT/pages/index.adoc | 8 +- integration-tests/pom.xml | 55 +-- .../it/JasperReportsResource.java | 159 +++++-- .../src/main/resources/CustomersReport.jasper | Bin 0 -> 20676 bytes .../src/main/resources/OrdersReport.jasper | Bin 0 -> 18988 bytes .../src/main/resources/application.properties | 5 +- .../it/JasperReportsResourceIT.java | 8 + pom.xml | 16 + runtime/pom.xml | 22 +- .../XalanTransformerFactory.java | 135 ++++++ .../graal/JRClassLoaderSubstitution.java | 34 ++ .../graal/TemplatesImplSubstitution.java | 24 ++ .../javax.xml.transform.TransformerFactory | 1 + 19 files changed, 1044 insertions(+), 113 deletions(-) create mode 100644 deployment/src/main/java/io/quarkiverse/jasperreports/deployment/BatikProcessor.java create mode 100644 deployment/src/main/java/io/quarkiverse/jasperreports/deployment/OpenPdfProcessor.java create mode 100644 deployment/src/main/java/io/quarkiverse/jasperreports/deployment/SaxonProcessor.java create mode 100644 deployment/src/main/java/io/quarkiverse/jasperreports/deployment/XalanProcessor.java create mode 100644 integration-tests/src/main/resources/CustomersReport.jasper create mode 100644 integration-tests/src/main/resources/OrdersReport.jasper create mode 100644 integration-tests/src/test/java/io/quarkiverse/jasperreports/it/JasperReportsResourceIT.java create mode 100644 runtime/src/main/java/io/quarkiverse/jasperreports/XalanTransformerFactory.java create mode 100644 runtime/src/main/java/io/quarkiverse/jasperreports/graal/JRClassLoaderSubstitution.java create mode 100644 runtime/src/main/java/io/quarkiverse/jasperreports/graal/TemplatesImplSubstitution.java create mode 100644 runtime/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory diff --git a/deployment/pom.xml b/deployment/pom.xml index 958f1b4..470d37c 100644 --- a/deployment/pom.xml +++ b/deployment/pom.xml @@ -61,4 +61,4 @@ - + \ No newline at end of file diff --git a/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/BatikProcessor.java b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/BatikProcessor.java new file mode 100644 index 0000000..c4c6cfd --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/BatikProcessor.java @@ -0,0 +1,86 @@ +package io.quarkiverse.jasperreports.deployment; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem; +import io.quarkus.logging.Log; + +public class BatikProcessor { + + @BuildStep + void indexTransitiveDependencies(BuildProducer index) { + index.produce(new IndexDependencyBuildItem("org.apache.xmlgraphics", "batik-bridge")); + index.produce(new IndexDependencyBuildItem("org.apache.xmlgraphics", "batik-gvt")); + } + + /** + * Registers classes and packages that need to be initialized at runtime. + * + * @param runtimeInitializedPackages Producer for runtime initialized packages + */ + @BuildStep + void runtimeInitializedClasses(BuildProducer runtimeInitializedPackages) { + //@formatter:off + List classes = new ArrayList<>(Stream.of("javax.swing", + "javax.swing.plaf.metal", + "javax.swing.text.html", + "javax.swing.text.rtf", + "sun.datatransfer", + "sun.swing", + org.apache.batik.anim.values.AnimatableTransformListValue.class.getPackageName(), + org.apache.batik.bridge.CSSUtilities.class.getPackageName(), + org.apache.batik.css.engine.SystemColorSupport.class.getPackageName(), + org.apache.batik.dom.svg.AbstractSVGTransform.class.getPackageName(), + org.apache.batik.ext.awt.MultipleGradientPaint.class.getPackageName(), + org.apache.batik.ext.awt.image.GraphicsUtil.class.getPackageName(), + org.apache.batik.ext.awt.image.rendered.TurbulencePatternRed.class.getPackageName(), + org.apache.batik.ext.awt.image.spi.DefaultBrokenLinkProvider.class.getName(), + org.apache.batik.gvt.CompositeGraphicsNode.class.getPackageName(), + org.apache.batik.gvt.renderer.MacRenderer.class.getPackageName(), + org.apache.batik.script.jpython.JPythonInterpreter.class.getPackageName(), + org.apache.batik.script.InterpreterPool.class.getName()).toList()); + //@formatter:on + Log.debugf("Runtime: %s", classes); + classes.stream() + .map(RuntimeInitializedPackageBuildItem::new) + .forEach(runtimeInitializedPackages::produce); + } + + @BuildStep + void registerForReflection(BuildProducer reflectiveClass, + CombinedIndexBuildItem combinedIndex) { + final List classNames = new ArrayList<>( + collectClassesInPackage(combinedIndex, org.apache.batik.gvt.font.AWTGVTFont.class.getPackageName())); + + // methods and fields + reflectiveClass.produce( + ReflectiveClassBuildItem.builder(classNames.toArray(new String[0])).methods().fields().build()); + } + + private List collectClassesInPackage(CombinedIndexBuildItem combinedIndex, String packageName) { + final List classes = new ArrayList<>(); + final List packages = new ArrayList<>(combinedIndex.getIndex().getSubpackages(packageName)); + packages.add(DotName.createSimple(packageName)); + for (DotName aPackage : packages) { + final List packageClasses = combinedIndex.getIndex() + .getClassesInPackage(aPackage) + .stream() + .map(ClassInfo::toString) + .toList(); + classes.addAll(packageClasses); + } + Log.tracef("Package: %s", classes); + return classes; + } + +} \ No newline at end of file diff --git a/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/JasperReportsProcessor.java b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/JasperReportsProcessor.java index 7e255c6..c9a6943 100644 --- a/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/JasperReportsProcessor.java +++ b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/JasperReportsProcessor.java @@ -8,9 +8,14 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -19,99 +24,237 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourcePatternsBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem; +import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.deployment.pkg.builditem.UberJarMergedResourceBuildItem; import io.quarkus.logging.Log; +import net.sf.jasperreports.compilers.ReportExpressionEvaluationData; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JasperReport; +import net.sf.jasperreports.engine.design.JRReportCompileData; +import net.sf.jasperreports.engine.util.JRLoader; +/** + * Processor class for JasperReports integration in Quarkus. + * This class handles various build steps required for JasperReports functionality, + * including feature registration, resource merging, dependency indexing, + * and reflection configuration. + */ class JasperReportsProcessor { private static final String FEATURE = "jasperreports"; private static final String EXTENSIONS_FILE = "jasperreports_extension.properties"; - private static final String DEFAULT_ROOT_PATH = "src"; + private static final String DEFAULT_ROOT_PATH = "src/main/resources"; @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FEATURE); } + /** + * Merges the JasperReports extension properties file into the Uber JAR. + *

+ * This build step ensures that the JasperReports extension properties file + * is included in the final Uber JAR, allowing JasperReports to properly + * load its extensions at runtime. + * + * @return A {@link UberJarMergedResourceBuildItem} representing the merged resource + */ @BuildStep UberJarMergedResourceBuildItem mergeResource() { return new UberJarMergedResourceBuildItem(EXTENSIONS_FILE); } + /** + * Indexes transitive dependencies required for JasperReports functionality. + *

+ * This method adds various JasperReports modules and related dependencies + * to the build index, ensuring they are properly processed during the build. + * + * @param index The BuildProducer for IndexDependencyBuildItem, used to add + * dependencies to the build index. + */ @BuildStep void indexTransitiveDependencies(BuildProducer index) { index.produce(new IndexDependencyBuildItem("net.sf.jasperreports", "jasperreports")); - index.produce(new IndexDependencyBuildItem("com.github.javaparser", "javaparser-core")); - index.produce(new IndexDependencyBuildItem("org.apache.xmlgraphics", "batik-bridge")); - index.produce(new IndexDependencyBuildItem("org.apache.xmlgraphics", "batik-gvt")); + index.produce(new IndexDependencyBuildItem("net.sf.jasperreports", "jasperreports-data-adapters")); + index.produce(new IndexDependencyBuildItem("net.sf.jasperreports", "jasperreports-excel-poi")); + index.produce(new IndexDependencyBuildItem("net.sf.jasperreports", "jasperreports-jdt")); + index.produce(new IndexDependencyBuildItem("net.sf.jasperreports", "jasperreports-pdf")); + index.produce(new IndexDependencyBuildItem("net.sf.jasperreports", "jasperreports-xalan")); } + /** + * Registers classes for reflection in the native image. + *

+ * This method collects various classes used by JasperReports and registers them + * for reflection. This is necessary for proper functioning in a native image context. + * + * @param reflectiveClass The BuildProducer for ReflectiveClassBuildItem, used to register + * classes for reflection. + * @param combinedIndex The CombinedIndexBuildItem containing information about indexed classes. + */ @BuildStep void registerForReflection(BuildProducer reflectiveClass, CombinedIndexBuildItem combinedIndex) { - + //@formatter:off final List classNames = new ArrayList<>(); - classNames.addAll(collectClassesInPackage(combinedIndex, org.apache.batik.gvt.font.AWTGVTFont.class.getPackageName())); - classNames.addAll(collectClassesInPackage(combinedIndex, - net.sf.jasperreports.engine.fonts.FontExtensionsRegistry.class.getPackageName())); - classNames.addAll(collectClassesInPackage(combinedIndex, - net.sf.jasperreports.engine.util.ImageUtil.class.getPackageName())); + // By Implementors: jasper interfaces/abstract classes that are created with Class.forName + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.design.JRCompiler.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.extensions.ExtensionsRegistry.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.extensions.ExtensionsRegistryFactory.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.JRDataSource.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.JRDataSourceProvider.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.JRVisitor.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.xml.ReportLoader.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.JRTemplate.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.query.QueryExecuterFactory.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.query.JRQueryExecuter.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, net.sf.jasperreports.engine.util.xml.JRXPathExecuterFactory.class.getName())); + classNames.addAll(collectSubclasses(combinedIndex, net.sf.jasperreports.engine.JRAbstractExporter.class.getName())); + + // By Package (utilities etc) + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.compilers.ReportExpressionEvaluationData.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.components.ComponentsExtensionsRegistryFactory.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.data.xmla.XmlaDataAdapterImpl.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.JasperReport.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.base.ElementStore.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.design.JRDesignQuery.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.export.DefaultExporterFilterFactory.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.export.ExporterNature.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.fill.JRFillVariable.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.fonts.SimpleFontFace.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.part.DefaultPartComponentsBundle.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.type.ColorEnum.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.util.ImageUtil.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.util.xml.JRXPathExecuter.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.engine.xml.ReportLoader.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.export.CsvExporterConfiguration.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.extensions.DefaultExtensionsRegistryFactory.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.governors.GovernorExtensionsRegistryFactory.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.jackson.util.JacksonUtil.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.parts.PartComponentsExtensionsRegistryFactory.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.pdf.classic.ClassicPdfProducerFactory.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.renderers.AbstractSvgDataToGraphics2DRenderer.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.renderers.util.SvgFontProcessor.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.repo.DefaultRepositoryExtensionsRegistryFactory.class.getPackageName())); + classNames.addAll(collectClassesInPackage(combinedIndex, net.sf.jasperreports.util.JsonLoader.class.getPackageName())); + + // basic Java classes found in reports + classNames.addAll(collectImplementors(combinedIndex, java.util.Collection.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, java.time.temporal.TemporalAccessor.class.getName())); + classNames.addAll(collectImplementors(combinedIndex, java.util.Map.Entry.class.getName())); + classNames.add(byte.class.getName()); + classNames.add(byte[].class.getName()); + classNames.add(java.awt.color.ColorSpace.class.getName()); + classNames.add(java.awt.Color.class.getName()); + classNames.add(java.io.Serializable.class.getName()); + classNames.add(java.lang.Boolean.class.getName()); + classNames.add(java.lang.Byte.class.getName()); + classNames.add(java.lang.Enum.class.getName()); + classNames.add(java.lang.Byte.class.getName()); + classNames.add(java.lang.Character.class.getName()); + classNames.add(java.lang.Double.class.getName()); + classNames.add(java.lang.Float.class.getName()); + classNames.add(java.lang.Integer.class.getName()); + classNames.add(java.lang.Iterable.class.getName()); + classNames.add(java.lang.Long.class.getName()); + classNames.add(java.lang.Number.class.getName()); + classNames.add(java.lang.Object.class.getName()); + classNames.add(java.lang.Short.class.getName()); + classNames.add(java.lang.String.class.getName()); + classNames.add(java.lang.StringBuilder.class.getName()); + classNames.add(java.math.BigDecimal.class.getName()); + classNames.add(java.util.AbstractList.class.getName()); + classNames.add(java.util.AbstractMap.class.getName()); + classNames.add(java.util.ArrayList.class.getName()); + classNames.add(java.util.Calendar.class.getName()); + classNames.add(java.util.Date.class.getName()); + classNames.add(java.util.GregorianCalendar.class.getName()); + classNames.add(java.util.HashMap.class.getName()); + classNames.add(java.util.HashSet.class.getName()); + classNames.add(java.util.Hashtable.class.getName()); + classNames.add(java.util.LinkedHashMap.class.getName()); + classNames.add(java.util.LinkedHashSet.class.getName()); + classNames.add(java.util.LinkedList.class.getName()); + classNames.add(java.util.List.class.getName()); + classNames.add(java.util.Map.class.getName()); + classNames.add(java.util.Set.class.getName()); + classNames.add(java.util.TreeMap.class.getName()); + classNames.add(java.util.UUID.class.getName()); + classNames.add(java.util.Vector.class.getName()); + + //@formatter:on + final TreeSet uniqueClasses = new TreeSet<>(classNames); + Log.debugf("Reflection: %s", uniqueClasses); reflectiveClass.produce( - ReflectiveClassBuildItem.builder(classNames.toArray(new String[0])).methods(true).serialization(true).build()); + ReflectiveClassBuildItem.builder(uniqueClasses.toArray(new String[0])).constructors().methods().fields() + .serialization().build()); } + /** + * Registers classes and packages that need to be initialized at runtime. + * + * @param runtimeInitializedPackages Producer for runtime initialized packages + * @param combinedIndex Combined index of classes in the application + */ @BuildStep - void runtimeInitializedClasses(BuildProducer runtimeInitializedPackages) { + void runtimeInitializedClasses(BuildProducer runtimeInitializedPackages, + CombinedIndexBuildItem combinedIndex) { //@formatter:off - Stream.of( - "javax.swing", - "javax.swing.plaf.metal", - "javax.swing.text.html", - "javax.swing.text.rtf", - "org.apache.hc.client5.http.impl.auth.NTLMEngineImpl", - "sun.datatransfer", - "sun.swing", - net.sf.jasperreports.components.headertoolbar.HeaderToolbarElement.class.getPackageName(), - net.sf.jasperreports.components.list.UnusedSpaceImageRenderer.class.getName(), - net.sf.jasperreports.components.util.AbstractFieldComparator.class.getPackageName(), - net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition.class.getPackageName(), - net.sf.jasperreports.engine.SimpleReportContext.class.getPackageName(), - net.sf.jasperreports.engine.base.ElementsBlock.class.getPackageName(), - net.sf.jasperreports.engine.convert.ReportConverter.class.getName(), - net.sf.jasperreports.engine.export.AbstractTextRenderer.class.getPackageName(), - net.sf.jasperreports.engine.fill.JRFillSubreport.class.getPackageName(), - net.sf.jasperreports.engine.fonts.AwtFontManager.class.getPackageName(), - net.sf.jasperreports.engine.type.ColorEnum.class.getPackageName(), - net.sf.jasperreports.engine.util.JRQueryExecuterUtils.class.getPackageName(), - org.apache.batik.anim.values.AnimatableTransformListValue.class.getPackageName(), - org.apache.batik.bridge.CSSUtilities.class.getPackageName(), - org.apache.batik.css.engine.SystemColorSupport.class.getPackageName(), - org.apache.batik.dom.svg.AbstractSVGTransform.class.getPackageName(), - org.apache.batik.ext.awt.MultipleGradientPaint.class.getPackageName(), - org.apache.batik.ext.awt.image.GraphicsUtil.class.getPackageName(), - org.apache.batik.ext.awt.image.rendered.TurbulencePatternRed.class.getPackageName(), - org.apache.batik.ext.awt.image.spi.DefaultBrokenLinkProvider.class.getName(), - org.apache.batik.gvt.CompositeGraphicsNode.class.getPackageName(), - org.apache.batik.gvt.renderer.MacRenderer.class.getPackageName(), - org.apache.batik.script.InterpreterPool.class.getName(), - org.apache.xmlbeans.impl.schema.TypeSystemHolder.class.getName() - ) + List classes = collectImplementors(combinedIndex, net.sf.jasperreports.extensions.ExtensionsRegistryFactory.class.getName()); + classes.addAll(Stream.of("javax.swing", + "javax.swing.plaf.metal", + "javax.swing.text.html", + "javax.swing.text.rtf", + "sun.datatransfer", + "sun.swing", + net.sf.jasperreports.components.headertoolbar.HeaderToolbarElement.class.getPackageName(), + net.sf.jasperreports.components.iconlabel.IconLabelElement.class.getPackageName(), + net.sf.jasperreports.components.list.UnusedSpaceImageRenderer.class.getName(), + net.sf.jasperreports.components.util.AbstractFieldComparator.class.getPackageName(), + net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition.class.getPackageName(), + net.sf.jasperreports.engine.SimpleReportContext.class.getPackageName(), + net.sf.jasperreports.engine.base.ElementsBlock.class.getPackageName(), + net.sf.jasperreports.engine.convert.ReportConverter.class.getName(), + net.sf.jasperreports.engine.export.AbstractTextRenderer.class.getPackageName(), + net.sf.jasperreports.engine.fill.JRFillSubreport.class.getPackageName(), + net.sf.jasperreports.engine.fonts.AwtFontManager.class.getPackageName(), + net.sf.jasperreports.engine.type.ColorEnum.class.getPackageName(), + net.sf.jasperreports.engine.util.JRQueryExecuterUtils.class.getPackageName(), + net.sf.jasperreports.poi.query.PoiQueryExecuterFactoryBundle.class.getName(), + net.sf.jasperreports.xalan.data.XalanXmlDataSource.class.getPackageName(), + net.sf.jasperreports.xalan.util.XalanNsAwareXPathExecuter.class.getPackageName(), + org.apache.batik.anim.values.AnimatableTransformListValue.class.getPackageName(), + org.apache.xmlbeans.impl.schema.TypeSystemHolder.class.getName()).toList()); + //@formatter:on + Log.debugf("Runtime: %s", classes); + classes.stream() .map(RuntimeInitializedPackageBuildItem::new) .forEach(runtimeInitializedPackages::produce); - //@formatter:on } + /** + * Registers various resource build items for native image compilation. + * + * @param nativeImageResourcePatterns Producer for native image resource patterns + * @param nativeImageResourceProducer Producer for native image resources + * @param resourceBundleBuildItem Producer for resource bundles + */ @BuildStep - void registerResourceBuildItems(BuildProducer nativeImageResourceProducer, + void registerResourceBuildItems(BuildProducer nativeImageResourcePatterns, + BuildProducer nativeImageResourceProducer, BuildProducer resourceBundleBuildItem) { + // Register individual resource files nativeImageResourceProducer.produce(new NativeImageResourceBuildItem( "jasperreports.properties", EXTENSIONS_FILE, @@ -121,20 +264,114 @@ void registerResourceBuildItems(BuildProducer nati "metadata_messages-defaults.properties", "properties-metadata.json")); + // Register resource bundles resourceBundleBuildItem.produce(new NativeImageResourceBundleBuildItem("jasperreports_messages")); resourceBundleBuildItem.produce(new NativeImageResourceBundleBuildItem("metadata_messages")); resourceBundleBuildItem.produce(new NativeImageResourceBundleBuildItem("metadata_messages-defaults")); + + // Register resource patterns for OOXML export + final NativeImageResourcePatternsBuildItem.Builder builder = NativeImageResourcePatternsBuildItem.builder(); + builder.includeGlob("**/export/ooxml/docx/**"); + builder.includeGlob("**/export/ooxml/pptx/**"); + builder.includeGlob("**/export/ooxml/xlsx/**"); + nativeImageResourcePatterns.produce(builder.build()); } + /** + * Registers report files for native image compilation and processes compiled report files. + * + * @param nativeImageResourcePatterns Producer for native image resource patterns + * @param additionalClasses Producer for additional generated classes + * @param reflectiveClassProducer Producer for reflective classes + * @param outputTarget The output target build item + */ @BuildStep - void registerReports(BuildProducer nativeImageResourcePatterns) { + void registerReports(BuildProducer nativeImageResourcePatterns, + BuildProducer additionalClasses, + BuildProducer reflectiveClassProducer, + OutputTargetBuildItem outputTarget) { final NativeImageResourcePatternsBuildItem.Builder builder = NativeImageResourcePatternsBuildItem.builder(); - builder.includeGlob("**/*." + ReportFileBuildItem.EXT_REPORT); - builder.includeGlob("**/*." + ReportFileBuildItem.EXT_COMPILED); - builder.includeGlob("**/*." + ReportFileBuildItem.EXT_STYLE); + builder.includeGlob("*." + ReportFileBuildItem.EXT_REPORT); + builder.includeGlob("*." + ReportFileBuildItem.EXT_COMPILED); + builder.includeGlob("*." + ReportFileBuildItem.EXT_STYLE); nativeImageResourcePatterns.produce(builder.build()); + + Path startDir = findProjectRoot(outputTarget.getOutputDirectory()); + Log.debugf("JasperReport Source Directory: %s", startDir); + Set foundFiles = new HashSet<>(); + + try { + // compiled - .jasper + Files.walkFileTree(startDir, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + // Check if the file has one of the desired extensions + String filePath = file.toString(); + if (filePath.endsWith(ReportFileBuildItem.EXT_COMPILED)) { + Log.debugf("Jasper compiled report: %s", filePath); + foundFiles.add(file); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + Log.error("Error looking for JasperReport files.", e); + } + + foundFiles.forEach((file) -> { + try { + String jasperFilePath = file.toFile().getAbsolutePath(); + JasperReport report = (JasperReport) JRLoader.loadObject(JRLoader.getLocationInputStream(jasperFilePath)); + JRReportCompileData reportData = (JRReportCompileData) report.getCompileData(); + ReportExpressionEvaluationData mainData = (ReportExpressionEvaluationData) reportData + .getMainDatasetCompileData(); + String reportDataClass = mainData.getCompileName(); + if (StringUtils.isNotBlank(reportDataClass)) { + byte[] bytes = (byte[]) mainData.getCompileData(); + Log.debugf("JasperReport Data Class: %s Size: %d", reportDataClass, bytes.length); + + reflectiveClassProducer + .produce(ReflectiveClassBuildItem.builder(reportDataClass).constructors().methods().serialization() + .build()); + additionalClasses.produce(new GeneratedClassBuildItem(true, reportDataClass, bytes)); + } + + } catch (JRException e) { + Log.error("Error loading JasperReport file class.", e); + } + }); } + /** + * Registers JasperReports proxy classes for native image compilation. + *

+ * This method collects classes from the CsvExporterConfiguration and PdfExporterConfiguration + * packages and registers them as proxy definitions for the native image build process. + * + * @param proxyDefinitions The producer for NativeImageProxyDefinitionBuildItem + * @param combinedIndex The combined index of classes for the build + */ + @BuildStep + void registerJasperReportsProxies(BuildProducer proxyDefinitions, + CombinedIndexBuildItem combinedIndex) { + // Register the proxy for exporters + List classes = new ArrayList<>(collectInterfacesInPackage(combinedIndex, + net.sf.jasperreports.export.CsvExporterConfiguration.class.getPackageName())); + classes.addAll(collectInterfacesInPackage(combinedIndex, + net.sf.jasperreports.pdf.PdfExporterConfiguration.class.getPackageName())); + for (String proxyClassName : classes) { + proxyDefinitions.produce(new NativeImageProxyDefinitionBuildItem(proxyClassName)); + } + } + + /** + * Registers font resources for inclusion in the native image. + *

+ * This method adds a glob pattern to include DejaVu fonts in the native image. + * These fonts are commonly used by JasperReports for PDF generation. + * + * @param nativeImageResourcePatterns The producer for NativeImageResourcePatternsBuildItem + */ @BuildStep void registerFonts(BuildProducer nativeImageResourcePatterns) { final NativeImageResourcePatternsBuildItem.Builder builder = NativeImageResourcePatternsBuildItem.builder(); @@ -144,7 +381,6 @@ void registerFonts(BuildProducer nativeIma @BuildStep(onlyIf = IsDevelopment.class) ReportRootBuildItem defaultReportRoot() { - return new ReportRootBuildItem(DEFAULT_ROOT_PATH); } @@ -198,6 +434,62 @@ private List collectClassesInPackage(CombinedIndexBuildItem combinedInde .toList(); classes.addAll(packageClasses); } + Log.tracef("Package: %s", classes); return classes; } + + private List collectInterfacesInPackage(CombinedIndexBuildItem combinedIndex, String packageName) { + final List classes = new ArrayList<>(); + final List packages = new ArrayList<>(combinedIndex.getIndex().getSubpackages(packageName)); + packages.add(DotName.createSimple(packageName)); + for (DotName aPackage : packages) { + final List packageClasses = combinedIndex.getIndex() + .getClassesInPackage(aPackage) + .stream() + .filter(ClassInfo::isInterface) // Filter only interfaces + .map(ClassInfo::toString) + .toList(); + classes.addAll(packageClasses); + } + Log.tracef("Package: %s", classes); + return classes; + } + + private List collectSubclasses(CombinedIndexBuildItem combinedIndex, String className) { + List classes = combinedIndex.getIndex() + .getAllKnownSubclasses(DotName.createSimple(className)) + .stream() + .map(ClassInfo::toString) + .collect(Collectors.toList()); + classes.add(className); + Log.tracef("Subclasses: %s", classes); + return classes; + } + + public List collectImplementors(CombinedIndexBuildItem combinedIndex, String className) { + Set classes = combinedIndex.getIndex() + .getAllKnownImplementors(DotName.createSimple(className)) + .stream() + .map(ClassInfo::toString) + .collect(Collectors.toCollection(HashSet::new)); + classes.add(className); + Set subclasses = new HashSet<>(); + for (String implementationClass : classes) { + subclasses.addAll(collectSubclasses(combinedIndex, implementationClass)); + } + classes.addAll(subclasses); + Log.tracef("Implementors: %s", classes); + return new ArrayList<>(classes); + } + + static Path findProjectRoot(Path outputDirectory) { + Path currentPath = outputDirectory.getParent(); + Log.tracef("Current Directory: %s", currentPath); + Path root = Paths.get(DEFAULT_ROOT_PATH); + if (Files.exists(currentPath.resolve(root))) { + return currentPath.resolve(root).normalize(); + } else { + return outputDirectory; + } + } } \ No newline at end of file diff --git a/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/OpenPdfProcessor.java b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/OpenPdfProcessor.java new file mode 100644 index 0000000..2854369 --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/OpenPdfProcessor.java @@ -0,0 +1,90 @@ +package io.quarkiverse.jasperreports.deployment; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.IndexDependencyBuildItem; +import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourcePatternsBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedPackageBuildItem; +import io.quarkus.logging.Log; + +public class OpenPdfProcessor { + + @BuildStep + void indexTransitiveDependencies(BuildProducer index) { + index.produce(new IndexDependencyBuildItem("com.github.librepdf", "openpdf")); + } + + @BuildStep + NativeImageEnableAllCharsetsBuildItem enableAllCharsetsBuildItem() { + return new NativeImageEnableAllCharsetsBuildItem(); + } + + @BuildStep + void registerFonts(BuildProducer nativeImageResourcePatterns) { + final NativeImageResourcePatternsBuildItem.Builder builder = NativeImageResourcePatternsBuildItem.builder(); + builder.includeGlob("**/pdf/fonts/**"); + builder.includeGlob("**/font-fallback/**"); + nativeImageResourcePatterns.produce(builder.build()); + } + + @BuildStep + void registerResources(BuildProducer resourceBundleBuildItem) { + // Register resource bundles + resourceBundleBuildItem.produce(new NativeImageResourceBundleBuildItem("com/lowagie/text/error_messages/en.lng")); + resourceBundleBuildItem.produce(new NativeImageResourceBundleBuildItem("com/lowagie/text/error_messages/nl.lng")); + resourceBundleBuildItem.produce(new NativeImageResourceBundleBuildItem("com/lowagie/text/error_messages/pt.lng")); + } + + /** + * Registers classes and packages that need to be initialized at runtime. + * + * @param runtimeInitializedPackages Producer for runtime initialized packages + */ + @BuildStep + void runtimeInitializedClasses(BuildProducer runtimeInitializedPackages) { + //@formatter:off + List classes = new ArrayList<>(); + classes.add(com.lowagie.text.pdf.PdfPublicKeySecurityHandler.class.getName()); + + Log.debugf("Runtime: %s", classes); + classes.stream() + .map(RuntimeInitializedPackageBuildItem::new) + .forEach(runtimeInitializedPackages::produce); + //@formatter:on + } + + @BuildStep + void registerForReflection(BuildProducer reflectiveClass, + CombinedIndexBuildItem combinedIndex) { + final List classNames = new ArrayList<>( + collectSubclasses(combinedIndex, com.lowagie.text.Image.class.getName())); + classNames.add(com.lowagie.text.PageSize.class.getName()); + classNames.add(com.lowagie.text.Utilities.class.getName()); + classNames.add(com.lowagie.text.pdf.PdfName.class.getName()); + + // methods and fields + reflectiveClass.produce( + ReflectiveClassBuildItem.builder(classNames.toArray(new String[0])).methods().fields().build()); + } + + public List collectSubclasses(CombinedIndexBuildItem combinedIndex, String className) { + List classes = combinedIndex.getIndex() + .getAllKnownSubclasses(DotName.createSimple(className)) + .stream() + .map(ClassInfo::toString) + .collect(Collectors.toList()); + classes.add(className); + return classes; + } +} \ No newline at end of file diff --git a/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/SaxonProcessor.java b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/SaxonProcessor.java new file mode 100644 index 0000000..f41a40d --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/SaxonProcessor.java @@ -0,0 +1,38 @@ +package io.quarkiverse.jasperreports.deployment; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; + +/** + * A build step class responsible for registering classes related to the Saxon library + * for reflection and runtime initialization during the Quarkus build process. + *

+ * Borrowed from Camel Quarkus XSLT Saxon extension. + *

+ */ +class SaxonProcessor { + + /** + * Registers the required Saxon and XML resolver classes for reflection and runtime initialization. + * + * @param reflectiveClasses the build producer for registering classes for reflection + * @param runtimeInitializedClasses the build producer for marking classes as needing runtime initialization + */ + @BuildStep + void registerSaxon(BuildProducer reflectiveClasses, + BuildProducer runtimeInitializedClasses) { + //@formatter:off + reflectiveClasses.produce(ReflectiveClassBuildItem.builder( + net.sf.saxon.Configuration.class, + net.sf.saxon.functions.String_1.class, + net.sf.saxon.functions.Tokenize_1.class, + net.sf.saxon.functions.StringJoin.class) + .build()); + reflectiveClasses.produce(ReflectiveClassBuildItem.builder(org.xmlresolver.loaders.XmlLoader.class).build()); + + runtimeInitializedClasses.produce(new RuntimeInitializedClassBuildItem("org.apache.hc.client5.http.impl.auth.NTLMEngineImpl")); + //@formatter:on + } +} \ No newline at end of file diff --git a/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/XalanProcessor.java b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/XalanProcessor.java new file mode 100644 index 0000000..0c077ce --- /dev/null +++ b/deployment/src/main/java/io/quarkiverse/jasperreports/deployment/XalanProcessor.java @@ -0,0 +1,78 @@ +package io.quarkiverse.jasperreports.deployment; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import io.quarkiverse.jasperreports.XalanTransformerFactory; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; +import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; + +/** + * Xalan native processor from Camel. + * + * @see XalanNativeImageProcessor.java + */ +class XalanProcessor { + private static final String TRANSFORMER_FACTORY_SERVICE_FILE_PATH = "META-INF/services/javax.xml.transform.TransformerFactory"; + + @BuildStep + List reflectiveClasses() { + return Arrays.asList( + ReflectiveClassBuildItem.builder(io.quarkiverse.jasperreports.XalanTransformerFactory.class.getName(), + "org.apache.xalan.xsltc.dom.ObjectFactory", + "org.apache.xalan.xsltc.dom.XSLTCDTMManager", + "org.apache.xalan.xsltc.trax.ObjectFactory", + "org.apache.xalan.xsltc.trax.TransformerFactoryImpl", + "org.apache.xml.dtm.ObjectFactory", + "org.apache.xml.dtm.ref.DTMManagerDefault", + "org.apache.xml.serializer.OutputPropertiesFactory", + "org.apache.xml.serializer.CharInfo", + "org.apache.xml.utils.FastStringBuffer").methods().build(), + ReflectiveClassBuildItem.builder("org.apache.xml.serializer.ToHTMLStream", + "org.apache.xml.serializer.ToTextStream", + "org.apache.xml.serializer.ToXMLStream").build()); + } + + @BuildStep + List resourceBundles() { + return Arrays.asList( + new NativeImageResourceBundleBuildItem("org.apache.xalan.xsltc.compiler.util.ErrorMessages"), + new NativeImageResourceBundleBuildItem("org.apache.xml.serializer.utils.SerializerMessages"), + new NativeImageResourceBundleBuildItem("org.apache.xml.serializer.XMLEntities"), + new NativeImageResourceBundleBuildItem("org.apache.xml.res.XMLErrorResources")); + } + + @BuildStep + void resources(BuildProducer resource) { + + Stream.of( + "html", + "text", + "xml", + "unknown") + .map(s -> "org/apache/xml/serializer/output_" + s + ".properties") + .map(NativeImageResourceBuildItem::new) + .forEach(resource::produce); + + } + + @BuildStep + void installTransformerFactory( + BuildProducer excludeConfig, + BuildProducer serviceProvider) { + + excludeConfig + .produce(new ExcludeConfigBuildItem("xalan\\.xalan-.*\\.jar", "/" + TRANSFORMER_FACTORY_SERVICE_FILE_PATH)); + serviceProvider.produce(new ServiceProviderBuildItem("javax.xml.transform.TransformerFactory", + XalanTransformerFactory.class.getName())); + + } + +} \ No newline at end of file diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc index e42dd56..f0819ba 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/modules/ROOT/pages/index.adoc @@ -19,7 +19,13 @@ For instance, with Maven, add the following dependency to your POM file: ---- -== Docker +== Compiling Reports + +Jasper does not recommend compiling `.jrxml` reports on the fly, but if necessary, it can be done only in JVM mode. This approach will NOT work in native mode, as there is no Java compiler available and the required classes for compilation are missing. The best practice is to use precompiled `.jasper` files instead. + +CAUTION: Compiling `.jrxml` files will not work in Native mode! + +== Native When building native images in Docker using the standard Quarkus Docker configuration files some additional features need to be installed to support fonts. Specifically font information is not included in https://developers.redhat.com/products/rhel/ubi[Red Hat's ubi-minimal images]. To install it simply add these lines to your `DockerFile.native` file: diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 706de61..4e3901f 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -57,12 +57,29 @@ build + generate-code + generate-code-tests + native-image-agent - org.apache.maven.plugins + maven-compiler-plugin + + true + + + + maven-surefire-plugin + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + maven-failsafe-plugin @@ -70,44 +87,29 @@ integration-test verify - - - ${project.build.directory}/${project.build.finalName}-runner - - org.jboss.logmanager.LogManager - ${maven.home} - - + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + - native-image + native native - - - - org.apache.maven.plugins - maven-surefire-plugin - - ${native.surefire.skip} - - - - false true - - --trace-object-instantiation=org.apache.batik.bridge.RhinoInterpreterFactory - @@ -157,6 +159,9 @@ true true + + --trace-object-instantiation=org.openxmlformats.schemas.wordprocessingml.x2006.main.STZoom$Enum + clean package @@ -189,4 +194,4 @@ - + \ No newline at end of file diff --git a/integration-tests/src/main/java/io/quarkiverse/jasperreports/it/JasperReportsResource.java b/integration-tests/src/main/java/io/quarkiverse/jasperreports/it/JasperReportsResource.java index 329c346..5f0c39b 100644 --- a/integration-tests/src/main/java/io/quarkiverse/jasperreports/it/JasperReportsResource.java +++ b/integration-tests/src/main/java/io/quarkiverse/jasperreports/it/JasperReportsResource.java @@ -17,16 +17,26 @@ package io.quarkiverse.jasperreports.it; import java.io.ByteArrayOutputStream; -import java.io.InputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import jakarta.enterprise.context.ApplicationScoped; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; import org.w3c.dom.Document; import io.quarkus.logging.Log; @@ -70,6 +80,7 @@ @Path("/jasperreports") @ApplicationScoped +@Produces(MediaType.APPLICATION_OCTET_STREAM) public class JasperReportsResource { private static final String TEST_REPORT_NAME = "CustomersReport"; @@ -77,11 +88,13 @@ public class JasperReportsResource { private JasperReport compile(String reportName) throws JRException { long start = System.currentTimeMillis(); - InputStream inputStream = JRLoader.getLocationInputStream(reportName + ".jrxml"); - JasperDesign jasperDesign = JRXmlLoader.load(inputStream); + JasperDesign jasperDesign = JRXmlLoader.load(JRLoader.getLocationInputStream(reportName + ".jrxml")); JasperReport jasperReport = JasperCompileManager.compileReport(jasperDesign); + // Save the compiled Jasper file + //JasperCompileManager.compileReportToFile(jasperDesign, reportName + ".jasper"); + Log.infof("Compilation time : %s", (System.currentTimeMillis() - start)); return jasperReport; @@ -89,9 +102,16 @@ private JasperReport compile(String reportName) throws JRException { private JasperPrint fill() throws JRException { long start = System.currentTimeMillis(); + JasperReport mainReport; + JasperReport subReport; - JasperReport mainReport = compile(TEST_REPORT_NAME); - JasperReport subReport = compile(TEST_SUB_REPORT_NAME); + if (isRunningInContainer()) { + mainReport = (JasperReport) JRLoader.loadObject(JRLoader.getLocationInputStream(TEST_REPORT_NAME + ".jasper")); + subReport = (JasperReport) JRLoader.loadObject(JRLoader.getLocationInputStream(TEST_SUB_REPORT_NAME + ".jasper")); + } else { + mainReport = compile(TEST_REPORT_NAME); + subReport = compile(TEST_SUB_REPORT_NAME); + } ReportContext reportContext = new SimpleReportContext(); @@ -116,27 +136,62 @@ private JasperPrint fill() throws JRException { return jasperPrint; } + /** + * Determines if the application is running inside a container (such as Docker or Kubernetes). + * This is done by inspecting the '/proc/1/cgroup' file and checking for the presence of + * "docker" or "kubepods". Additionally, it checks specific environment variables to verify + * the container environment. + * + * @return {@code true} if the application is running inside a container; {@code false} otherwise. + */ + public static boolean isRunningInContainer() { + if (isNativeImage()) + return true; + + try { + List lines = Files.readAllLines(Paths.get("/proc/1/cgroup")); + for (String line : lines) { + if (line.contains("docker") || line.contains("kubepods")) { + return true; + } + } + } catch (IOException e) { + // Ignore, likely not in a container if the file doesn't exist + } + + // check environment variables + return System.getenv("CONTAINER") != null || System.getenv("KUBERNETES_SERVICE_HOST") != null; + } + + private static boolean isNativeImage() { + return System.getProperty("org.graalvm.nativeimage.imagecode") != null; + } + @GET @Path("csv") - public byte[] csv() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response csv() throws JRException { long start = System.currentTimeMillis(); - JasperPrint jasperPrint = fill(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + JasperPrint jasperPrint = fill(); JRCsvExporter exporter = new JRCsvExporter(); - exporter.setExporterInput(new SimpleExporterInput(jasperPrint)); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); exporter.setExporterOutput(new SimpleWriterExporterOutput(outputStream)); exporter.exportReport(); Log.infof("CSV creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.csv"); + response.header("Content-Type", "text/csv"); + return response.build(); } @GET @Path("xml") - public byte[] xml(@QueryParam("embedded") boolean embedded) throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response xml(@QueryParam("embedded") boolean embedded) throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -150,12 +205,16 @@ public byte[] xml(@QueryParam("embedded") boolean embedded) throws JRException { exporter.setExporterOutput(xmlOutput); exporter.exportReport(); Log.infof("XML creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.xml"); + response.header("Content-Type", "application/xml"); + return response.build(); } @GET @Path("html") - public byte[] html() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response html() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -166,12 +225,16 @@ public byte[] html() throws JRException { exporter.exportReport(); Log.infof("HTML creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.html"); + response.header("Content-Type", "text/html"); + return response.build(); } @GET @Path("rtf") - public byte[] rtf() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response rtf() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -187,12 +250,16 @@ public byte[] rtf() throws JRException { Log.infof("RTF creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.rtf"); + response.header("Content-Type", "application/rtf"); + return response.build(); } @GET @Path("odt") - public byte[] odt() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response odt() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -205,12 +272,16 @@ public byte[] odt() throws JRException { exporter.exportReport(); Log.infof("ODT creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.odt"); + response.header("Content-Type", "application/vnd.oasis.opendocument.text"); + return response.build(); } @GET @Path("ods") - public byte[] ods() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response ods() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -226,12 +297,16 @@ public byte[] ods() throws JRException { exporter.exportReport(); Log.infof("ODS creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.ods"); + response.header("Content-Type", "application/vnd.oasis.opendocument.spreadsheet"); + return response.build(); } @GET @Path("docx") - public byte[] docx() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response docx() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -244,12 +319,16 @@ public byte[] docx() throws JRException { exporter.exportReport(); Log.infof("DOCX creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.docx"); + response.header("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + return response.build(); } @GET @Path("xlsx") - public byte[] xlsx() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response xlsx() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -265,12 +344,16 @@ public byte[] xlsx() throws JRException { exporter.exportReport(); Log.infof("XLSX creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.xlsx"); + response.header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + return response.build(); } @GET @Path("pptx") - public byte[] pptx() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response pptx() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -285,21 +368,26 @@ public byte[] pptx() throws JRException { Log.infof("PPTX creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.pptx"); + response.header("Content-Type", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); + return response.build(); } @GET @Path("print") + @Produces(MediaType.TEXT_PLAIN) public String print() throws JRException { long start = System.currentTimeMillis(); JasperPrintManager.printReport("CustomersReport.jrprint", true); - Log.infof("Printing time : %s", (System.currentTimeMillis() - start)); - return "Printed"; + Log.info("Printing time : " + (System.currentTimeMillis() - start)); + return "Printing time : " + (System.currentTimeMillis() - start); } @GET @Path("xls") - public byte[] xls() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response xls() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -316,12 +404,16 @@ public byte[] xls() throws JRException { Log.infof("XLS creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.xls"); + response.header("Content-Type", "application/vnd.ms-excel"); + return response.build(); } @GET @Path("pdf") - public byte[] pdf() throws JRException { + @APIResponse(responseCode = "200", description = "Document downloaded", content = @Content(mediaType = MediaType.APPLICATION_OCTET_STREAM, schema = @Schema(type = SchemaType.STRING, format = "binary"))) + public Response pdf() throws JRException { long start = System.currentTimeMillis(); JasperPrint jasperPrint = fill(); @@ -335,7 +427,10 @@ public byte[] pdf() throws JRException { Log.infof("PDF creation time : %s", (System.currentTimeMillis() - start)); - return outputStream.toByteArray(); + final Response.ResponseBuilder response = Response.ok(outputStream.toByteArray()); + response.header("Content-Disposition", "attachment;filename=jasper.pdf"); + response.header("Content-Type", "application/pdf"); + return response.build(); } } \ No newline at end of file diff --git a/integration-tests/src/main/resources/CustomersReport.jasper b/integration-tests/src/main/resources/CustomersReport.jasper new file mode 100644 index 0000000000000000000000000000000000000000..be693ac29e4af24f0169de594c5dd0622e24ba1c GIT binary patch literal 20676 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 TsTHbw=#&amNQu%ap*Q~rKvIv( literal 0 HcmV?d00001 diff --git a/integration-tests/src/main/resources/OrdersReport.jasper b/integration-tests/src/main/resources/OrdersReport.jasper new file mode 100644 index 0000000000000000000000000000000000000000..597eca56b4714333940365778176495523ea7fd9 GIT binary patch literal 18988 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 literal 0 HcmV?d00001 diff --git a/integration-tests/src/main/resources/application.properties b/integration-tests/src/main/resources/application.properties index 8be9549..a1fa7b4 100644 --- a/integration-tests/src/main/resources/application.properties +++ b/integration-tests/src/main/resources/application.properties @@ -1,8 +1,11 @@ quarkus.application.name=JasperReports IT +quarkus.native.resources.includes=*.xml # LOGGING quarkus.log.file.enable=false quarkus.log.file.path=it.log quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss} %-5p [%c] (%t) %s%e%n quarkus.log.level=INFO -quarkus.log.category."net.sf.jasperreports".level=INFO \ No newline at end of file +%prod.quarkus.log.category."net.sf.jasperreports".level=DEBUG +quarkus.log.category."net.sf.jasperreports.engine.util.JRClassLoader".level=DEBUG +quarkus.log.category."io.quarkus.deployment.steps.ReflectiveHierarchyStep".level=ERROR \ No newline at end of file diff --git a/integration-tests/src/test/java/io/quarkiverse/jasperreports/it/JasperReportsResourceIT.java b/integration-tests/src/test/java/io/quarkiverse/jasperreports/it/JasperReportsResourceIT.java new file mode 100644 index 0000000..aefb648 --- /dev/null +++ b/integration-tests/src/test/java/io/quarkiverse/jasperreports/it/JasperReportsResourceIT.java @@ -0,0 +1,8 @@ +package io.quarkiverse.jasperreports.it; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class JasperReportsResourceIT extends JasperReportsResourceTest { + // Execute the same tests but in packaged mode. +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4f9d360..c62759a 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 3.15.1 2.0.6 + 2.7.2 7.0.1 @@ -89,6 +90,21 @@ jasperreports-fonts ${version.jasperreports} + + net.sf.jasperreports + jasperreports-annotation-processors + ${version.jasperreports} + + + xalan + xalan + ${version.xalan} + + + xalan + serializer + ${version.xalan} + diff --git a/runtime/pom.xml b/runtime/pom.xml index b328bbc..e640e15 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -124,6 +124,21 @@ icu4j 75.1 + + org.apache.xmlgraphics + fop + 2.10 + + + org.bouncycastle + bcprov-jdk18on + 1.78.1 + + + org.bouncycastle + bcpkix-jdk18on + 1.78.1 + net.sf.saxon Saxon-HE @@ -145,6 +160,11 @@ 2.7.6 + + org.graalvm.sdk + nativeimage + provided + @@ -181,4 +201,4 @@ - + \ No newline at end of file diff --git a/runtime/src/main/java/io/quarkiverse/jasperreports/XalanTransformerFactory.java b/runtime/src/main/java/io/quarkiverse/jasperreports/XalanTransformerFactory.java new file mode 100644 index 0000000..89bef5f --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/jasperreports/XalanTransformerFactory.java @@ -0,0 +1,135 @@ +package io.quarkiverse.jasperreports; + +import javax.xml.transform.ErrorListener; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.URIResolver; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TemplatesHandler; +import javax.xml.transform.sax.TransformerHandler; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.XMLFilter; + +/** + * A {@link TransformerFactory} delegating to a {@link TransformerFactory} created via + * {@code TransformerFactory.newInstance("org.apache.xalan.xsltc.trax.TransformerFactoryImpl", Thread.currentThread().getContextClassLoader())} + * + * @see XalanTransformerFactory.java + */ +public final class XalanTransformerFactory extends SAXTransformerFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(XalanTransformerFactory.class); + + private final SAXTransformerFactory delegate; + + public XalanTransformerFactory() { + final SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance( + "org.apache.xalan.xsltc.trax.TransformerFactoryImpl", + Thread.currentThread().getContextClassLoader()); + try { + factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); + } catch (TransformerException e) { + LOGGER.warn("Unsupported TransformerFactory feature " + javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING); + } + + this.delegate = factory; + } + + @Override + public Transformer newTransformer(Source source) throws TransformerConfigurationException { + return delegate.newTransformer(source); + } + + @Override + public Transformer newTransformer() throws TransformerConfigurationException { + return delegate.newTransformer(); + } + + @Override + public Templates newTemplates(Source source) throws TransformerConfigurationException { + return delegate.newTemplates(source); + } + + @Override + public Source getAssociatedStylesheet(Source source, String media, String title, String charset) + throws TransformerConfigurationException { + return delegate.getAssociatedStylesheet(source, media, title, charset); + } + + @Override + public void setURIResolver(URIResolver resolver) { + delegate.setURIResolver(resolver); + } + + @Override + public URIResolver getURIResolver() { + return delegate.getURIResolver(); + } + + @Override + public void setFeature(String name, boolean value) throws TransformerConfigurationException { + delegate.setFeature(name, value); + } + + @Override + public boolean getFeature(String name) { + return delegate.getFeature(name); + } + + @Override + public void setAttribute(String name, Object value) { + delegate.setAttribute(name, value); + } + + @Override + public Object getAttribute(String name) { + return delegate.getAttribute(name); + } + + @Override + public void setErrorListener(ErrorListener listener) { + delegate.setErrorListener(listener); + } + + @Override + public ErrorListener getErrorListener() { + return delegate.getErrorListener(); + } + + @Override + public TransformerHandler newTransformerHandler(Source source) throws TransformerConfigurationException { + return delegate.newTransformerHandler(source); + } + + @Override + public TransformerHandler newTransformerHandler(Templates templates) throws TransformerConfigurationException { + return delegate.newTransformerHandler(templates); + } + + @Override + public TransformerHandler newTransformerHandler() throws TransformerConfigurationException { + return delegate.newTransformerHandler(); + } + + @Override + public TemplatesHandler newTemplatesHandler() throws TransformerConfigurationException { + return delegate.newTemplatesHandler(); + } + + @Override + public XMLFilter newXMLFilter(Source source) throws TransformerConfigurationException { + return delegate.newXMLFilter(source); + } + + @Override + public XMLFilter newXMLFilter(Templates templates) throws TransformerConfigurationException { + return delegate.newXMLFilter(templates); + } + +} \ No newline at end of file diff --git a/runtime/src/main/java/io/quarkiverse/jasperreports/graal/JRClassLoaderSubstitution.java b/runtime/src/main/java/io/quarkiverse/jasperreports/graal/JRClassLoaderSubstitution.java new file mode 100644 index 0000000..71edf7a --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/jasperreports/graal/JRClassLoaderSubstitution.java @@ -0,0 +1,34 @@ +package io.quarkiverse.jasperreports.graal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Substitution class for net.sf.jasperreports.engine.util.JRClassLoader. + * JasperReports loads classes by bytes out of .jasper files which is not supported by native images. + * This substitution class will load the classes using the current thread's context class loader. + */ +@TargetClass(className = "net.sf.jasperreports.engine.util.JRClassLoader") +final class JRClassLoaderSubstitution { + + /** + * Loads a class with the specified name using the current thread's context class loader. + * + * @param className The fully qualified name of the class to load. + * @param bytecodes The bytecodes of the class (not used in this implementation). + * @return The loaded Class object. + * @throws RuntimeException if the class cannot be found. + */ + @Substitute + Class loadClass(String className, byte[] bytecodes) { + Class clazz; + + try { + clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + return clazz; + } +} \ No newline at end of file diff --git a/runtime/src/main/java/io/quarkiverse/jasperreports/graal/TemplatesImplSubstitution.java b/runtime/src/main/java/io/quarkiverse/jasperreports/graal/TemplatesImplSubstitution.java new file mode 100644 index 0000000..8cc9c9d --- /dev/null +++ b/runtime/src/main/java/io/quarkiverse/jasperreports/graal/TemplatesImplSubstitution.java @@ -0,0 +1,24 @@ +package io.quarkiverse.jasperreports.graal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Substitution class for org.apache.xalan.xsltc.trax.TemplatesImpl. + * This class is used to load classes by bytes out of .jasper files which is not supported by native images. + * This substitution class will throw an UnsupportedOperationException when trying to define a class. + * + * @see XalanTransformerFactory.java + */ +@TargetClass(className = "org.apache.xalan.xsltc.trax.TemplatesImpl") +final class TemplatesImplSubstitution { + + @TargetClass(className = "org.apache.xalan.xsltc.trax.TemplatesImpl", innerClass = "TransletClassLoader") + static final class TransletClassLoader { + @Substitute + Class defineClass(final byte[] b) { + throw new UnsupportedOperationException(); + } + } +} \ No newline at end of file diff --git a/runtime/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory b/runtime/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory new file mode 100644 index 0000000..e9e2083 --- /dev/null +++ b/runtime/src/main/resources/META-INF/services/javax.xml.transform.TransformerFactory @@ -0,0 +1 @@ +io.quarkiverse.jasperreports.XalanTransformerFactory \ No newline at end of file