dotCMS / dotCMS/core

ACheckerConnectionFactory swallows exceptions and drops the cause chain, masking H2 startup failures

Open
#35,431 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

OKR : Customer Support stale Team : Maintenance
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Problem Statement

Exception handling in ACheckerConnectionFactory drops the original failure in two different ways: getConn() catches the exception, prints it to stderr, and returns null; getConnection(), closeConnection(), and getDbVersion() rethrow with new SQLException(e.toString()) which discards the cause chain. As a result, when the AChecker H2 pool fails to initialize, the real stack trace never reaches the logs, and downstream code NPEs on a null Connection producing a secondary error that masks the actual root cause.

Actual Behavior

The real H2 failure (in our reproduction case: NullPointerException: Cannot invoke "Object.hashCode()" because "key" is null at DROP TABLE IF EXISTS AC_check_examples [50000-224]) is printed via e.printStackTrace() to stderr only — it does not go through the application logger and in many deployments is not captured in the log aggregator at all.
getConn() returns null after the catch block.
getConnection() assigns that null to its static connection field and returns it.
The caller in DAOImpl.execute() calls conn.createStatement() on the null reference.
The only exception that surfaces in logs is a NullPointerException from DAOImpl.java:58, several frames away from the real fault. There is no causal link back to the original H2 error.

The defective code is in dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/dao/ACheckerConnectionFactory.java:

// lines 54–80 — getConn() swallows and returns null
private static Connection getConn() throws Exception {
    if ( pool == null ) {
        try {
            ...
            return pool.getConnection();
        } catch (Exception e) {
            e.printStackTrace();                                                       // line 71
            System.err.println("Unable to start db properly : " + e.getMessage());     // line 72
        }
    } else {
        return pool.getConnection();
    }
    return null;                                                                        // line 79
}

// line 45 — getConnection() strips the cause
throw new SQLException(e.toString());

// line 94 — closeConnection() strips the cause
throw new SQLException(e.toString());

// line 107 — getDbVersion() strips the cause
throw new Exception(e.toString());

And in dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/dao/DAOImpl.java:57-58:

Connection conn  = ACheckerConnectionFactory.getConnection(); // can be null
st = conn.createStatement();                                   // → NPE, masking the real failure

This also violates the project rule in CLAUDE.md: "Config/Logger only: Never System.out, System.getProperty, or System.getenv."

Steps to Reproduce
  • Put the AChecker H2 pool into a state where JdbcConnectionPool.create(...) or the initial pool.getConnection() will throw — for example on RHEL 8.10 with SELinux enforcing on an XFS filesystem (the environment in Freshdesk #36298), or by pointing achecker.sql at an unreadable file, or by placing an incompatible .mv.db file in dotsecure/h2db/1/.
  • Start dotCMS.
  • Open a content type that uses the TinyMCE editor (this triggers ACheckerAPIImpl.getAccessibilityGuidelineList → GuidelinesDAO.getOpenGuidelines → ACheckerConnectionFactory.getConnection).
  • Inspect the application logs.
Acceptance Criteria

Scenario 1 — pool creation fails

Given H2 pool creation throws (e.g. unreadable achecker.sql)
When any caller invokes ACheckerConnectionFactory.getConnection()
Then a SQLException is thrown whose getCause() is the original H2 exception
And the real stack trace appears in the dotCMS application log via Logger.error(...)
And no NullPointerException surfaces from DAOImpl.execute

Scenario 2 — pool created but pool.getConnection() fails

Given the pool exists but a transient JDBC failure occurs
When a caller invokes getConnection()
Then a SQLException is thrown with the original cause attached
And the log line names ACheckerConnectionFactory and includes the full stack trace

Scenario 3 — close failure

Given connection.close() throws
When closeConnection() is called
Then a SQLException is thrown whose getCause() is the original exception

Scenario 4 — happy path

Given H2 starts successfully
When a user opens the TinyMCE editor
Then AChecker loads normally with no behaviour change and no new log noise

Scenario 5 — no stderr output

Given any failure path in ACheckerConnectionFactory
When the error is handled
Then no System.err / System.out / e.printStackTrace() calls execute — all output goes through com.dotmarketing.util.Logger

dotCMS Version

core-26.04.11-01

Severity

Medium - Some functionality impacted

Links

https://dotcms.freshdesk.com/a/tickets/36435
https://dotcms.freshdesk.com/a/tickets/36298

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with dotCMS/src/enterprise/java/com/dotcms/enterprise/achecker/dao/ACheckerConnectionFactory.java, especially getConn(), getConnection(), closeConnection(), and getDbVersion(), then inspect DAOImpl.java:57-58. Trace the pool creation and connection failure paths and the project's com.dotmarketing.util.Logger usage. Done means original causes remain attached, failures use application logging without stderr or printStackTrace(), and DAOImpl no longer receives a null connection.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.