BIRT Report Engine API createFactoryObject returns null

159 Views Asked by At

I'm trying to use BIRT's Report Engine API to run and render reports from a Java program.
I'm using Gradle 8.5 and JDK 21 to build and run the program.
I have no problem executing the program and creating reports using Gradle's run task, but I'm having issues trying to create a single executable .jar file.

The code for initializing the Report Engine:

EngineConfig engineConfig = new EngineConfig();
engineConfig.setResourcePath(getConfigValue("resource_path")); // Returns path to the resource folder for reports

Platform.startup(engineConfig);

// THIS METHOD RETURNS NULL
IReportEngineFactory engineFactory = (IReportEngineFactory) Platform
        .createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);

// HERE I GET A NullPointerException
// java.lang.NullPointerException: Cannot invoke "org.eclipse.birt.report.engine.api.IReportEngineFactory.createReportEngine(org.eclipse.birt.report.engine.api.EngineConfig)" because "engineFactory" is null
engine = engineFactory.createReportEngine(engineConfig);

build.gradle

plugins {
    id 'com.github.johnrengelman.shadow' version '8.1.1'
    id 'application'
}

version = '1.0.0'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.slf4j:slf4j-simple:2.0.9'
    implementation 'com.rabbitmq:amqp-client:5.20.0'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.16.0'
    implementation 'info.picocli:picocli:4.7.5'
    implementation fileTree('lib') { include '*.jar' }
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

application {
    mainClass = 'report.engine.Main'
}

shadowJar {
    zip64 true
    archiveClassifier.set('standalone')
}

The lib folder referenced in the last dependency contains all the .jar files inside birt-runtime-4.14.0/ReportEngine/lib
The task shadowJar completes fine and creates the .jar file with all the dependencies inside, but when I run it with java -jar "/path/to/fat.jar" I get that NullPointerException and the execution fails.

Am I doing something wrong? Or is there a better way to bundle all these .jar files from the BIRT Runtime (that are not available in a Maven repository) together with my code and the other dependencies into a single executable .jar file?
Any input is much appreciated :)


Update

After some more testing, I found out that if I add the file org.eclipse.birt.runtime_4.14.0-202312020807.jar from the folder birt-runtime-4.14.0/ReportEngine/lib to the folder where my fat jar is, and execute it with the exact same command, then the createFactoryObject method doesn't return null anymore and I don't get an exception.
In this case, if I then try to run and render a report that in a normal situation (executing the main class and adding the folder with dependencies to the classpath) would work without problems, I get another exception from the engine. The log:

2023-12-28 07:40:52 | [FINE]    EngineTask.setParameterValue: theme=1_default [java.lang.String]
2023-12-28 07:40:52 | [FINE]    EngineTask.setParameterValue: ids=[Ljava.lang.Object;@669ed014 [[Ljava.lang.Object;]
2023-12-28 07:40:53 | [FINE]    Running the report with paramters: theme:1_default
ids:[Ljava.lang.Object;@669ed014

2023-12-28 07:40:53 | [FINE]    handle type: org.eclipse.birt.report.model.api.OdaDataSetHandle
2023-12-28 07:40:53 | [FINE]    Start to prepare a PreparedQuery.
2023-12-28 07:40:53 | [FINE]    Finished preparing the PreparedQuery.
2023-12-28 07:40:53 | [FINE]    Finished preparing query.
2023-12-28 07:40:53 | [SEVERE]  An error happened while running the report. Cause:
2023-12-28 07:40:53 | [SEVERE]  org.eclipse.birt.report.engine.api.EngineException: Error happened while running the report.
    at org.eclipse.birt.report.engine.api.impl.EngineTask.handleFatalExceptions(EngineTask.java:1867)
    at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.doRun(RunAndRenderTask.java:153)
    at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.run(RunAndRenderTask.java:69)
    at ivertix.bi.report.engine.ReportTask.run(ReportTask.java:73)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.NullPointerException: Cannot read the array length because "extensions" is null
    at org.eclipse.datatools.connectivity.oda.spec.manifest.ResultExtensionExplorer.addAllExtensions(ResultExtensionExplorer.java:541)
    at org.eclipse.datatools.connectivity.oda.spec.manifest.ResultExtensionExplorer.<init>(ResultExtensionExplorer.java:122)
    at org.eclipse.datatools.connectivity.oda.spec.manifest.ResultExtensionExplorer.getInstance(ResultExtensionExplorer.java:86)
    at org.eclipse.birt.data.engine.impl.DataEngineImpl.getValidationContext(DataEngineImpl.java:681)
    at org.eclipse.birt.data.engine.impl.OdaDataSetRuntime.<init>(OdaDataSetRuntime.java:54)
    at org.eclipse.birt.data.engine.impl.DataSetRuntime.newInstance(DataSetRuntime.java:356)
    at org.eclipse.birt.data.engine.impl.PreparedDataSourceQuery.configureDataSetCache(PreparedDataSourceQuery.java:198)
    at org.eclipse.birt.data.engine.impl.PreparedDataSourceQuery.execute(PreparedDataSourceQuery.java:149)
    at org.eclipse.birt.data.engine.impl.PreparedOdaDSQuery.execute(PreparedOdaDSQuery.java:172)
    at org.eclipse.birt.report.data.adapter.impl.DataRequestSessionImpl.execute(DataRequestSessionImpl.java:594)
    at org.eclipse.birt.report.engine.data.dte.DteDataEngine.doExecuteQuery(DteDataEngine.java:138)
    at org.eclipse.birt.report.engine.data.dte.AbstractDataEngine.execute(AbstractDataEngine.java:254)
    at org.eclipse.birt.report.engine.executor.ExecutionContext.executeQuery(ExecutionContext.java:1624)
    at org.eclipse.birt.report.engine.executor.QueryItemExecutor.executeQuery(QueryItemExecutor.java:73)
    at org.eclipse.birt.report.engine.executor.TableItemExecutor.execute(TableItemExecutor.java:60)
    at org.eclipse.birt.report.engine.internal.executor.dup.SuppressDuplicateItemExecutor.execute(SuppressDuplicateItemExecutor.java:41)
    at org.eclipse.birt.report.engine.internal.executor.wrap.WrappedReportItemExecutor.execute(WrappedReportItemExecutor.java:45)
    at org.eclipse.birt.report.engine.internal.executor.l18n.LocalizedReportItemExecutor.execute(LocalizedReportItemExecutor.java:34)
    at org.eclipse.birt.report.engine.layout.html.HTMLBlockStackingLM.layoutNodes(HTMLBlockStackingLM.java:62)
    at org.eclipse.birt.report.engine.layout.html.HTMLPageLM.layout(HTMLPageLM.java:92)
    at org.eclipse.birt.report.engine.layout.html.HTMLReportLayoutEngine.layout(HTMLReportLayoutEngine.java:97)
    at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.doRun(RunAndRenderTask.java:145)
    ... 5 more

It seems like the problem is that the .jar files from ReportEngine/lib are inside my fat jar and not alongside it in the same folder.
In fact, if I run my fat jar with java -jar "/path/to/fat.jar" and inside its folder there are all the .jar files from ReportEngine/lib, I get no error and I render the report without problems.
Two things to note:

  1. Executing java with the -jar command ignores the classpath
  2. Inside the fat jar's folder the other external dependencies (not from BIRT) are NOT present, they are only inside the jar, and they work fine.

It seems strange that there's this difference between the other dependencies (slf4j, jackson, rabbitmq, ...) and the BIRT Runtime.
Any idea why that's the case?
Our goal is to produce a single executable .jar file so that, since we wanna run this program as a service, we can just deploy the single file and run it without additional steps. I think this is a pretty standard approach, and find it weird that the BIRT Runtime can't be used like this (although I'm no Java expert so it's possible that I'm overlooking something).
Any suggestions?

0

There are 0 best solutions below