EN · DE · RU · FR · ES

#800: ExceptionHelper.java

projectforge-business/src/main/java/org/projectforge/framework/utils/ExceptionHelper.java · 97 lines · 50 code · 39 comments · 8 blank
Three static utility methods for exception handling: filtered stack traces (collapse irrelevant frames and skip CGLIB proxies), stack trace to String (standard printStackTrace-to-writer), and recursive root cause resolution. Used primarily by the security/access layer to produce readable error messages that show only ProjectForge code paths, not Spring/Hibernate/Tomcat internals.

Methods

MethodWhat it doesWhy it exists
getFilteredStackTrace(ex, namespace)Builds a stack trace string containing only frames from the given namespace. Consecutive non-matching frames are collapsed into "at ...". Frames containing CGLIB$$ are always skipped — these are Hibernate proxy classes with mangled names like AddressDO$$EnhancerByCGLIB$$a1b2c3d4.Access violation errors would otherwise show 50+ lines of Spring Security and Tomcat internals before showing the single relevant ProjectForge line. The filtered trace shows only the developer-relevant call chain.
printStackTrace(ex)Converts a full stack trace to String via StringWriter+PrintWriter. Standard Java pattern.Logging frameworks need String, not PrintStream. Used when exceptions need to be serialized to JSON or stored in database columns.
getRootCause(ex)Recursively unwraps getCause() until the innermost exception is found. Returns the original exception if no cause exists.Spring wraps exceptions heavily (UndeclaredThrowableException, InvocationTargetException, DataAccessException). The real error is always at the bottom. This method extracts it.

Namespace Filtering Algorithm

The getFilteredStackTrace method uses a state-machine approach with two modes:

Normal mode: Printing frames. When a non-matching frame is encountered, it outputs "at ..." (collapsing the irrelevant frames) and switches to ignored mode.
Ignored mode: Skipping frames silently until a matching frame appears, then switching back to normal mode and printing it.

This produces compact output like: at org.projectforge.access.AccessChecker.check(AccessChecker.java:79) at ... at org.projectforge.task.TaskDao.hasAccess(TaskDao.java:176) — where "at ..." replaces all the Spring/Tomcat/Hibernate frames between the two relevant lines.

Consumers

Called by the access control layer (AccessCheckerImpl, AccessException) when building error messages for permission violations. Also used by ScriptExecutor to produce readable error output for Groovy/Kotlin script failures — without the filtered trace, a simple NPE in a script would produce a 200-line stack trace dominated by Groovy runtime and Spring proxy internals.

Git History

CommitWhat changed
868d6abb7Copyright 2025→2026
63081666fCopyright 2024→2025
b6092df09Copyright 2023→2024
ab45d51faCopyright 2001-2022→2001-2023
5f7ef41b8Copyright 2021→2022
ceb63e8a1Copyright 2001-2021
7c79f192Copyright 2020
73a9755dCode cleanup pass across all utils. Collapsed identical catch blocks, replaced ArrayList<Class> with diamond operator ArrayList<>, replaced StringBuffer with StringBuilder, switched Collections.sort to List.sort. The ExceptionHelper code itself uses StringBuilder in getFilteredStackTrace — this commit ensured consistency with the project-wide migration from synchronized StringBuffer to unsynchronized StringBuilder.
Gotcha — CGLIB$$ detection: The ignore() method checks for CGLIB$$ in the class name. When ProjectForge upgrades to Hibernate 6.x, proxies will use ByteBuddy instead of CGLIB — the class names will contain $HibernateProxy$ or $ByteBuddy$ instead. The ignore() method will need updating to handle both patterns.