Skip to content

MX-287: Isolate BIRT report execution from shared template state#483

Merged
IOhacker merged 2 commits into
openMF:developfrom
raghavvag:MX-287-execution-isolation
Jun 8, 2026
Merged

MX-287: Isolate BIRT report execution from shared template state#483
IOhacker merged 2 commits into
openMF:developfrom
raghavvag:MX-287-execution-isolation

Conversation

@raghavvag

@raghavvag raghavvag commented Jun 5, 2026

Copy link
Copy Markdown
Member

Summary

This PR addresses MX-287 by eliminating mutation of shared BIRT report design state during report execution.

Previously, the plugin cached IReportRunnable instances and then modified the underlying ReportDesignHandle with tenant-specific datasource properties before rendering. Since the same cached report instance could be reused across requests, this created a risk of shared mutable state and potential datasource leakage between concurrent report executions.

Problem

Current execution flow:

loadReport()
  -> cached IReportRunnable
  -> getDesignHandle()
  -> mutate datasource properties
  -> createRunAndRenderTask()
  -> render

The cached runnable's design handle was being modified directly.

In BIRT 4.23, createRunAndRenderTask() does not automatically clone the underlying design model. As a result, concurrent executions could potentially operate on shared mutable datasource configuration.

Solution

Introduced execution-local report creation.

New execution flow:

loadReport()
  -> cached template runnable
  -> copy ReportDesignHandle
  -> open execution-local runnable
  -> configure datasource
  -> create task
  -> render

Changes included:

  • Added BirtReportExecutionFactory
  • Created execution-local IReportRunnable instances from copied ReportDesignHandles
  • Updated BirtReportingProcessServiceImpl to use execution-local runnables
  • Added explicit IRunAndRenderTask.close() cleanup
  • Changed datasource configuration failures from silent logging to fail-fast behavior
  • Added unit tests covering execution isolation

Validation

Unit Tests

Added:

  • BirtReportExecutionFactoryTest
  • Updated BirtReportingProcessServiceImplTest

Validation includes:

  • Execution copies use independent design handles
  • Cached template is not mutated
  • Multiple execution copies remain isolated
  • Tasks are closed after rendering

Local Runtime Validation

Tested against a local Apache Fineract 1.12 deployment with the BIRT reporting plugin enabled.

Verified:

  • Plugin startup successful
  • Report generation successful
  • HTML rendering successful
  • Existing report functionality unchanged
  • No datasource configuration errors observed
  • Concurrent report executions complete successfully

Caching Status

This PR does not introduce or modify cache implementation.

The goal of MX-287 is execution isolation.

The solution is intentionally designed so that if caching is enabled or improved in the future (e.g. Spring Cache, Caffeine, or other caching strategies), execution-level datasource isolation will already be guaranteed.

In other words:

Cached Template
      ↓
Execution Copy
      ↓
Datasource Injection
      ↓
Render

This architecture remains safe regardless of future cache optimizations.

Scope

Included:

  • Execution-local BIRT runnable creation
  • Datasource isolation
  • Task lifecycle cleanup
  • Isolation-focused unit tests

Not included:

  • Cache redesign
  • Caffeine integration
  • Custom ODA datasource implementation
  • Report parameter refactoring
  • Renderer changes
Screenshot 2026-06-05 030019 Screenshot 2026-06-05 040200 Screenshot 2026-06-05 040228

Summary by CodeRabbit

  • New Features
    • Added a factory to create isolated, per-execution report runnables ensuring each report run uses its own copy of the report design.
  • Bug Fixes
    • Datasource configuration failures now surface as explicit errors instead of being silently logged.
    • Report execution closes BIRT tasks after rendering to improve stability and prevent resource leaks.
  • Tests
    • Added and updated tests verifying runnable isolation and proper task closure.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 34d5bff2-d546-4f99-9b24-b5314729ddf6

📥 Commits

Reviewing files that changed from the base of the PR and between 73e5e9c and 1beb4fe.

📒 Files selected for processing (2)
  • src/main/java/org/apache/fineract/infrastructure/report/service/BirtDataSourceConfigurer.java
  • src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactory.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactory.java

📝 Walkthrough

Walkthrough

Adds BirtReportExecutionFactory to produce execution-local BIRT runnables by copying cached designs, integrates it into BirtReportingProcessServiceImpl with safe task closure, changes BirtDataSourceConfigurer to throw on datasource configuration failures, and adds/updates tests validating factory isolation and task closure.

Changes

BIRT Report Execution Factory and Integration

Layer / File(s) Summary
BirtReportExecutionFactory component and createExecutionRunnable method
src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactory.java
New Spring component loads cached report templates and creates isolated execution copies of design handles. Sets execution filenames, opens designs via IReportEngine, rethrows PlatformDataIntegrityException, and reports other failures through ReportErrorHandler.
Factory integration in BirtReportingProcessServiceImpl
src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImpl.java
Replaces BirtReportLoader with BirtReportExecutionFactory for obtaining execution runnables, configures datasources on the execution design, initializes task to null and adds a finally block to safely close the IRunAndRenderTask, logging warnings on closure failures.
Error handling in BirtDataSourceConfigurer
src/main/java/org/apache/fineract/infrastructure/report/service/BirtDataSourceConfigurer.java
On datasource configuration failure, throws error via reportErrorHandler.reportError(...) with message key error.msg.reporting.datasource.configuration.failed and a datasource-specific message, using the caught exception as cause.
BirtReportExecutionFactoryTest for factory behavior
src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactoryTest.java
New unit tests verify execution runnables use copied independent design handles, mutations to execution copies do not affect cached templates, and repeated calls produce distinct runnables and handles.
BirtReportingProcessServiceImplTest updates for factory
src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImplTest.java
Tests updated to mock BirtReportExecutionFactory instead of loader; export-format, file-not-found, collaborator-order, and task-closure tests adjusted to the factory-based execution flow.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing execution-local BIRT report handling to prevent shared template state mutation, which is the core objective of this pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImpl.java (1)

70-73: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve PlatformDataIntegrityException instead of rewrapping it.

This catch block currently rewrites domain-specific PlatformDataIntegrityExceptions (from execution factory/datasource configuration) into error.msg.reporting.error, which drops actionable error codes and breaks error-contract fidelity.

Suggested fix
-    } catch (Exception e) {
+    } catch (PlatformDataIntegrityException e) {
+      throw e;
+    } catch (Exception e) {
       log.error("Failed to generate BIRT report: {}", reportName, e);
       throw new PlatformDataIntegrityException(
           "error.msg.reporting.error", "Report generation failed: " + e.getMessage(), e);
     } finally {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImpl.java`
around lines 70 - 73, The catch in BirtReportingProcessServiceImpl currently
catches Exception and always wraps it into a new PlatformDataIntegrityException,
losing original PlatformDataIntegrityException details; change the handler in
the method containing this catch so that if the caught exception is already a
PlatformDataIntegrityException you rethrow it unchanged (throw
(PlatformDataIntegrityException) e), otherwise wrap
non-PlatformDataIntegrityException instances as you do now (log and throw new
PlatformDataIntegrityException). Update the catch block around the report
generation logic (the try/catch that logs "Failed to generate BIRT report" and
throws PlatformDataIntegrityException) to perform an instanceof check and
rethrow existing PlatformDataIntegrityException to preserve its error codes and
cause.
🧹 Nitpick comments (2)
src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImplTest.java (1)

199-214: ⚡ Quick win

Add a task-close test for renderer failure path.

Current coverage validates task.close() only after successful rendering. Add a case where renderer.render(...) throws and still verify task.close() is invoked from finally.

As per coding guidelines, "Verify both happy path and edge cases."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImplTest.java`
around lines 199 - 214, Add a new test that ensures the IRunAndRenderTask is
closed even when the renderer fails: in the test class
BirtReportingProcessServiceImplTest create a test (e.g.,
shouldCloseTaskAfterRenderingWhenRendererFails) that mocks IReportRunnable
(design), ReportDesignHandle, and IRunAndRenderTask, stubs
reportExecutionFactory.createExecutionRunnable(...) to return design and
reportEngine.createRunAndRenderTask(design) to return task, then make
pdfRenderer.render(...) throw an exception (e.g., RuntimeException) when called,
invoke service.processRequest("sample", queryParams("PDF")) (allowing the
exception or asserting it is thrown), and finally verify(task).close() is
invoked in the failure path; reuse the same mock setup as in
shouldCloseTaskAfterRendering and assert close() is called in the finally path.
src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactoryTest.java (1)

38-116: ⚡ Quick win

Add explicit failure-path tests for factory exception behavior.

Please add tests for: (1) PlatformDataIntegrityException is propagated unchanged, and (2) non-platform exceptions are converted through reportErrorHandler.reportError(...).

As per coding guidelines, "Verify both happy path and edge cases."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactoryTest.java`
around lines 38 - 116, Add two tests for
executionFactory.createExecutionRunnable: (1) mock reportLoader.loadReport(...)
to throw a PlatformDataIntegrityException and assert that
createExecutionRunnable propagates that same PlatformDataIntegrityException (no
wrapping or conversion); (2) mock reportLoader.loadReport(...) to throw a
non-platform exception (e.g., RuntimeException), stub
reportErrorHandler.reportError(originalException) to return a different
Throwable and assert that createExecutionRunnable calls
reportErrorHandler.reportError with the original exception and then throws the
exception returned by reportErrorHandler.reportError; reference the existing
symbols executionFactory.createExecutionRunnable, reportLoader.loadReport,
reportErrorHandler.reportError, and PlatformDataIntegrityException to locate
where to add the tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImpl.java`:
- Around line 70-73: The catch in BirtReportingProcessServiceImpl currently
catches Exception and always wraps it into a new PlatformDataIntegrityException,
losing original PlatformDataIntegrityException details; change the handler in
the method containing this catch so that if the caught exception is already a
PlatformDataIntegrityException you rethrow it unchanged (throw
(PlatformDataIntegrityException) e), otherwise wrap
non-PlatformDataIntegrityException instances as you do now (log and throw new
PlatformDataIntegrityException). Update the catch block around the report
generation logic (the try/catch that logs "Failed to generate BIRT report" and
throws PlatformDataIntegrityException) to perform an instanceof check and
rethrow existing PlatformDataIntegrityException to preserve its error codes and
cause.

---

Nitpick comments:
In
`@src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactoryTest.java`:
- Around line 38-116: Add two tests for
executionFactory.createExecutionRunnable: (1) mock reportLoader.loadReport(...)
to throw a PlatformDataIntegrityException and assert that
createExecutionRunnable propagates that same PlatformDataIntegrityException (no
wrapping or conversion); (2) mock reportLoader.loadReport(...) to throw a
non-platform exception (e.g., RuntimeException), stub
reportErrorHandler.reportError(originalException) to return a different
Throwable and assert that createExecutionRunnable calls
reportErrorHandler.reportError with the original exception and then throws the
exception returned by reportErrorHandler.reportError; reference the existing
symbols executionFactory.createExecutionRunnable, reportLoader.loadReport,
reportErrorHandler.reportError, and PlatformDataIntegrityException to locate
where to add the tests.

In
`@src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImplTest.java`:
- Around line 199-214: Add a new test that ensures the IRunAndRenderTask is
closed even when the renderer fails: in the test class
BirtReportingProcessServiceImplTest create a test (e.g.,
shouldCloseTaskAfterRenderingWhenRendererFails) that mocks IReportRunnable
(design), ReportDesignHandle, and IRunAndRenderTask, stubs
reportExecutionFactory.createExecutionRunnable(...) to return design and
reportEngine.createRunAndRenderTask(design) to return task, then make
pdfRenderer.render(...) throw an exception (e.g., RuntimeException) when called,
invoke service.processRequest("sample", queryParams("PDF")) (allowing the
exception or asserting it is thrown), and finally verify(task).close() is
invoked in the failure path; reuse the same mock setup as in
shouldCloseTaskAfterRendering and assert close() is called in the finally path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 220508ad-bf3b-4b0d-949d-cc768eda3c47

📥 Commits

Reviewing files that changed from the base of the PR and between 4c190bb and 73e5e9c.

📒 Files selected for processing (5)
  • src/main/java/org/apache/fineract/infrastructure/report/service/BirtDataSourceConfigurer.java
  • src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactory.java
  • src/main/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImpl.java
  • src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportExecutionFactoryTest.java
  • src/test/java/org/apache/fineract/infrastructure/report/service/BirtReportingProcessServiceImplTest.java

@raghavvag

Copy link
Copy Markdown
Member Author

@dr-fuch @IOhacker please review this pr

@dr-fuch

dr-fuch commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Reviewing pr

@dr-fuch dr-fuch left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM @IOhacker

@IOhacker IOhacker merged commit 86590fc into openMF:develop Jun 8, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants