From f16cac8c87ec9f69fb6a7acd95349fb2a70dc4ec Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Wed, 20 May 2026 06:28:27 -0600 Subject: [PATCH 1/5] fix(velocity): fail loud when global macro library load fails (#35601) VelocimacroFactory silently swallowed ResourceNotFoundException when loading velocimacro.library files at engine init, allowing init to "succeed" with no global macros registered. After K8s pod restarts with transient I/O errors (notably on EFS-backed volumes), #renderMarks and #editContentlet rendered as literal text on every page until the JVM was restarted. The catch now logs the failed library name at ERROR and tracks failures so init can throw VelocityException when any required library failed. A new flag velocimacro.library.fail-on-missing (default true) lets self-hosted operators preserve the legacy silent-warn behavior if they intentionally configure optional libraries. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../velocity/runtime/RuntimeConstants.java | 7 ++ .../velocity/runtime/VelocimacroFactory.java | 27 +++++- dotCMS/src/main/resources/system.properties | 4 + .../runtime/VelocimacroFactoryTest.java | 89 +++++++++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java diff --git a/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java b/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java index 3b6e6f2bb885..888076e1333d 100644 --- a/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java +++ b/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java @@ -277,6 +277,13 @@ public interface RuntimeConstants /** switch for autoloading library-sourced VMs (for development). */ String VM_LIBRARY_AUTORELOAD = "velocimacro.library.autoreload"; + /** + * Fail engine init when a configured velocimacro.library file cannot be loaded. + * Default: true (matches Apache Velocity's original fail-fast behavior for missing + * macro libraries). When false, the legacy silent-warn behavior is preserved. + */ + String VM_LIBRARY_FAIL_ON_MISSING = "velocimacro.library.fail-on-missing"; + /** boolean (true/false) default true : allow inline (in-template) macro definitions. */ String VM_PERM_ALLOW_INLINE = "velocimacro.permissions.allow.inline"; diff --git a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java index 3cc83fa2e292..a86cc44a8eeb 100644 --- a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java +++ b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java @@ -172,6 +172,11 @@ else if (libfiles instanceof String) macroLibVec.add(libfiles); } + final boolean failOnMissing = rsvc.getBoolean( + RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING, true); + final List loadedLibraries = new ArrayList(); + final List failedLibraries = new ArrayList(); + for(int i = 0, is = macroLibVec.size(); i < is; i++) { String lib = (String) macroLibVec.get(i); @@ -205,9 +210,16 @@ else if (libfiles instanceof String) twonk.template = template; twonk.modificationTime = template.getLastModified(); libModMap.put(lib, twonk); + loadedLibraries.add(lib); } catch(ResourceNotFoundException rnse){ - Logger.warn(this.getClass(),rnse.getMessage()); + // Surfaces the failed library by name so operators can grep startup logs. + // See dotCMS issue #35601 — previously this was a generic WARN that + // produced silent macro-rendering failures after pod restarts. + Logger.error(this.getClass(), + "Velocimacro : VM library not found : " + lib + + " : " + rnse.getMessage()); + failedLibraries.add(lib); } catch (Exception e) { @@ -221,6 +233,19 @@ else if (libfiles instanceof String) vmManager.setRegisterFromLib(false); } } + + Logger.info(this, + "Velocimacro libraries loaded: " + loadedLibraries + + " ; failed: " + failedLibraries); + + if (failOnMissing && !failedLibraries.isEmpty()) + { + throw new VelocityException( + "Velocimacro : required VM libraries failed to load: " + + failedLibraries + + ". Set " + RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING + + "=false to allow startup with missing libraries."); + } } /* diff --git a/dotCMS/src/main/resources/system.properties b/dotCMS/src/main/resources/system.properties index 9d572d60d500..ca9c5f8136a9 100644 --- a/dotCMS/src/main/resources/system.properties +++ b/dotCMS/src/main/resources/system.properties @@ -118,6 +118,10 @@ output.encoding=UTF-8 velocimacro.library.autoreload=false velocimacro.library=VM_global_library.vm,dotCMS_library.vm,dotCMS_library_ext.vm +# When true (default), engine init throws VelocityException if any velocimacro.library +# file fails to load — prevents silent macro-rendering failures on pod restart (issue #35601). +# Set to false only if you intentionally configure optional/missing library files. +velocimacro.library.fail-on-missing=true velocimacro.permissions.allow.inline.to.replace.global=true directive.parse.max.depth=100 directive.if.tostring.nullcheck=false diff --git a/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java new file mode 100644 index 000000000000..bb0fbfc171b8 --- /dev/null +++ b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java @@ -0,0 +1,89 @@ +package org.apache.velocity.runtime; + +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for the {@code velocimacro.library.fail-on-missing} flag added in issue #35601. + * + *

The dotCMS-patched {@link VelocimacroFactory} previously swallowed + * {@link ResourceNotFoundException} when loading any configured {@code velocimacro.library} + * file, allowing engine init to succeed while leaving global macros unregistered. After + * pod restarts with transient I/O errors (notably EFS-backed K8s volumes), + * {@code #renderMarks} and {@code #editContentlet} would render as literal text on every + * page until the JVM was restarted. + * + *

The flag defaults to {@code true} — engine init now throws {@link VelocityException}. + * Operators that intentionally configure optional libraries may opt out with + * {@code velocimacro.library.fail-on-missing=false}. + */ +public class VelocimacroFactoryTest { + + private static final String MISSING_LIBRARY = "this-library-does-not-exist.vm"; + + private RuntimeServices rsvc; + + @Before + public void setUp() { + rsvc = mock(RuntimeServices.class); + when(rsvc.getProperty(RuntimeConstants.VM_LIBRARY)).thenReturn(MISSING_LIBRARY); + when(rsvc.getTemplate(MISSING_LIBRARY)) + .thenThrow(new ResourceNotFoundException( + "Cannot find resource '" + MISSING_LIBRARY + "'")); + // Permission flags read after the library loop — return defaults so init completes + // when fail-on-missing=false. + when(rsvc.getBoolean(anyString(), anyBoolean())).thenAnswer(invocation -> + invocation.getArgument(1)); + } + + @Test + public void initThrowsWhenFailOnMissingIsTrue() { + when(rsvc.getBoolean(eq(RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING), anyBoolean())) + .thenReturn(true); + + final VelocimacroFactory factory = new VelocimacroFactory(rsvc); + try { + factory.initVelocimacro(); + fail("Expected VelocityException when " + RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING + + "=true and a velocimacro.library file fails to load"); + } catch (VelocityException expected) { + assertTrue( + "Exception message should name the failed library: " + expected.getMessage(), + expected.getMessage().contains(MISSING_LIBRARY)); + } + } + + @Test + public void initThrowsByDefault() { + // Default value passed to getBoolean is true — the test setUp returns it as-is, + // simulating the absence of an explicit property. + final VelocimacroFactory factory = new VelocimacroFactory(rsvc); + try { + factory.initVelocimacro(); + fail("Expected VelocityException by default when a velocimacro.library file fails to load"); + } catch (VelocityException expected) { + assertTrue(expected.getMessage().contains(MISSING_LIBRARY)); + } + } + + @Test + public void initSucceedsWhenFailOnMissingIsFalse() { + when(rsvc.getBoolean(eq(RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING), anyBoolean())) + .thenReturn(false); + + // Legacy silent-warn behavior — engine init completes even though the macro + // library could not be loaded. + final VelocimacroFactory factory = new VelocimacroFactory(rsvc); + factory.initVelocimacro(); + } +} From a79866ac15227c589a6ec9798c524958709c6dfd Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Wed, 20 May 2026 06:36:42 -0600 Subject: [PATCH 2/5] style(velocity): use diamond operator for ArrayList init (#35601) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/org/apache/velocity/runtime/VelocimacroFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java index a86cc44a8eeb..ff2a710d015f 100644 --- a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java +++ b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java @@ -174,8 +174,8 @@ else if (libfiles instanceof String) final boolean failOnMissing = rsvc.getBoolean( RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING, true); - final List loadedLibraries = new ArrayList(); - final List failedLibraries = new ArrayList(); + final List loadedLibraries = new ArrayList<>(); + final List failedLibraries = new ArrayList<>(); for(int i = 0, is = macroLibVec.size(); i < is; i++) { From fc5206a2cc5836f6a28a2d73313c05ab4cf4e9ef Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Thu, 21 May 2026 10:17:56 -0600 Subject: [PATCH 3/5] fix(velocity): gate fail-loud behind Config flag, default off (#35601) Addresses PR #35768 review feedback: - Switch the fail-on-missing flag from a Velocity runtime property (rsvc.getBoolean / system.properties) to a dotCMS feature flag read via Config.getBooleanProperty("VELOCITY_LIBRARY_FAIL_ON_MISSING"). - Default to false to preserve historical behavior. Existing customers with optional/missing macro libraries continue to start normally; operators opt in once they have verified their library files load cleanly. Removes the now-unused RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING and the corresponding system.properties entry. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../velocity/runtime/RuntimeConstants.java | 7 --- .../velocity/runtime/VelocimacroFactory.java | 13 +++-- dotCMS/src/main/resources/system.properties | 4 -- .../runtime/VelocimacroFactoryTest.java | 54 +++++++++++-------- 4 files changed, 41 insertions(+), 37 deletions(-) diff --git a/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java b/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java index 888076e1333d..3b6e6f2bb885 100644 --- a/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java +++ b/dotCMS/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java @@ -277,13 +277,6 @@ public interface RuntimeConstants /** switch for autoloading library-sourced VMs (for development). */ String VM_LIBRARY_AUTORELOAD = "velocimacro.library.autoreload"; - /** - * Fail engine init when a configured velocimacro.library file cannot be loaded. - * Default: true (matches Apache Velocity's original fail-fast behavior for missing - * macro libraries). When false, the legacy silent-warn behavior is preserved. - */ - String VM_LIBRARY_FAIL_ON_MISSING = "velocimacro.library.fail-on-missing"; - /** boolean (true/false) default true : allow inline (in-template) macro definitions. */ String VM_PERM_ALLOW_INLINE = "velocimacro.permissions.allow.inline"; diff --git a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java index ff2a710d015f..6895331a9827 100644 --- a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java +++ b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java @@ -20,6 +20,7 @@ */ import com.dotcms.rendering.velocity.services.DotResourceLoader; +import com.dotmarketing.util.Config; import com.dotmarketing.util.Logger; import java.io.StringReader; import java.util.ArrayList; @@ -172,8 +173,12 @@ else if (libfiles instanceof String) macroLibVec.add(libfiles); } - final boolean failOnMissing = rsvc.getBoolean( - RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING, true); + // Feature flag: when true, engine init throws VelocityException if any + // configured velocimacro.library file fails to load. Default is false to + // preserve historical behavior; operators opt in once they've verified + // their library files load cleanly (see issue #35601). + final boolean failOnMissing = Config.getBooleanProperty( + "VELOCITY_LIBRARY_FAIL_ON_MISSING", false); final List loadedLibraries = new ArrayList<>(); final List failedLibraries = new ArrayList<>(); @@ -243,8 +248,8 @@ else if (libfiles instanceof String) throw new VelocityException( "Velocimacro : required VM libraries failed to load: " + failedLibraries - + ". Set " + RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING - + "=false to allow startup with missing libraries."); + + ". Set VELOCITY_LIBRARY_FAIL_ON_MISSING=false (or unset)" + + " to allow startup with missing libraries."); } } diff --git a/dotCMS/src/main/resources/system.properties b/dotCMS/src/main/resources/system.properties index ca9c5f8136a9..9d572d60d500 100644 --- a/dotCMS/src/main/resources/system.properties +++ b/dotCMS/src/main/resources/system.properties @@ -118,10 +118,6 @@ output.encoding=UTF-8 velocimacro.library.autoreload=false velocimacro.library=VM_global_library.vm,dotCMS_library.vm,dotCMS_library_ext.vm -# When true (default), engine init throws VelocityException if any velocimacro.library -# file fails to load — prevents silent macro-rendering failures on pod restart (issue #35601). -# Set to false only if you intentionally configure optional/missing library files. -velocimacro.library.fail-on-missing=true velocimacro.permissions.allow.inline.to.replace.global=true directive.parse.max.depth=100 directive.if.tostring.nullcheck=false diff --git a/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java index bb0fbfc171b8..ef5306beb7c7 100644 --- a/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java +++ b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java @@ -1,9 +1,12 @@ package org.apache.velocity.runtime; +import com.dotmarketing.util.Config; import org.apache.velocity.exception.ResourceNotFoundException; import org.apache.velocity.exception.VelocityException; +import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.mockito.MockedStatic; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -11,10 +14,12 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; /** - * Tests for the {@code velocimacro.library.fail-on-missing} flag added in issue #35601. + * Tests for the {@code VELOCITY_LIBRARY_FAIL_ON_MISSING} dotCMS feature flag added in + * issue #35601. * *

The dotCMS-patched {@link VelocimacroFactory} previously swallowed * {@link ResourceNotFoundException} when loading any configured {@code velocimacro.library} @@ -23,15 +28,18 @@ * {@code #renderMarks} and {@code #editContentlet} would render as literal text on every * page until the JVM was restarted. * - *

The flag defaults to {@code true} — engine init now throws {@link VelocityException}. - * Operators that intentionally configure optional libraries may opt out with - * {@code velocimacro.library.fail-on-missing=false}. + *

The flag defaults to {@code false} — engine init preserves the legacy silent-warn + * behavior so existing customers are not broken by this PR. Operators opt in with + * {@code VELOCITY_LIBRARY_FAIL_ON_MISSING=true} (env var or system property) once they + * have verified their library files load cleanly. */ public class VelocimacroFactoryTest { + private static final String FLAG_KEY = "VELOCITY_LIBRARY_FAIL_ON_MISSING"; private static final String MISSING_LIBRARY = "this-library-does-not-exist.vm"; private RuntimeServices rsvc; + private MockedStatic configMock; @Before public void setUp() { @@ -40,21 +48,30 @@ public void setUp() { when(rsvc.getTemplate(MISSING_LIBRARY)) .thenThrow(new ResourceNotFoundException( "Cannot find resource '" + MISSING_LIBRARY + "'")); - // Permission flags read after the library loop — return defaults so init completes - // when fail-on-missing=false. + // Permission flags read after the library loop — return the supplied default + // so init completes when the fail-on-missing flag is off. when(rsvc.getBoolean(anyString(), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(1)); + configMock = mockStatic(Config.class); + // Default behavior: return the supplied default for any flag we did not stub. + configMock.when(() -> Config.getBooleanProperty(anyString(), anyBoolean())) + .thenAnswer(invocation -> invocation.getArgument(1)); + } + + @After + public void tearDown() { + configMock.close(); } @Test - public void initThrowsWhenFailOnMissingIsTrue() { - when(rsvc.getBoolean(eq(RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING), anyBoolean())) + public void initThrowsWhenFlagIsTrue() { + configMock.when(() -> Config.getBooleanProperty(eq(FLAG_KEY), anyBoolean())) .thenReturn(true); final VelocimacroFactory factory = new VelocimacroFactory(rsvc); try { factory.initVelocimacro(); - fail("Expected VelocityException when " + RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING + fail("Expected VelocityException when " + FLAG_KEY + "=true and a velocimacro.library file fails to load"); } catch (VelocityException expected) { assertTrue( @@ -64,25 +81,18 @@ public void initThrowsWhenFailOnMissingIsTrue() { } @Test - public void initThrowsByDefault() { - // Default value passed to getBoolean is true — the test setUp returns it as-is, - // simulating the absence of an explicit property. + public void initDoesNotThrowByDefault() { + // Default behavior — flag is off, legacy silent-warn path preserved. + // The flag default (false) flows through the configMock's default thenAnswer. final VelocimacroFactory factory = new VelocimacroFactory(rsvc); - try { - factory.initVelocimacro(); - fail("Expected VelocityException by default when a velocimacro.library file fails to load"); - } catch (VelocityException expected) { - assertTrue(expected.getMessage().contains(MISSING_LIBRARY)); - } + factory.initVelocimacro(); } @Test - public void initSucceedsWhenFailOnMissingIsFalse() { - when(rsvc.getBoolean(eq(RuntimeConstants.VM_LIBRARY_FAIL_ON_MISSING), anyBoolean())) + public void initDoesNotThrowWhenFlagIsExplicitlyFalse() { + configMock.when(() -> Config.getBooleanProperty(eq(FLAG_KEY), anyBoolean())) .thenReturn(false); - // Legacy silent-warn behavior — engine init completes even though the macro - // library could not be loaded. final VelocimacroFactory factory = new VelocimacroFactory(rsvc); factory.initVelocimacro(); } From 0e6fccea2596f9404bce369458151a8ce0dabcf5 Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Thu, 21 May 2026 11:25:50 -0600 Subject: [PATCH 4/5] style(velocity): align Logger arg + pin test load-loop contract (#35601) Addresses PR #35768 review feedback: - VelocimacroFactory: pass `this` to Logger.error in the ResourceNotFoundException catch, matching the adjacent catch block and the repo-wide convention. - VelocimacroFactoryTest: assert `verify(rsvc).getTemplate(MISSING_LIBRARY)` on the no-throw paths so a future refactor that short-circuits the load loop can't silently pass these tests. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../org/apache/velocity/runtime/VelocimacroFactory.java | 2 +- .../apache/velocity/runtime/VelocimacroFactoryTest.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java index 6895331a9827..9687d8f54eb4 100644 --- a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java +++ b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java @@ -221,7 +221,7 @@ else if (libfiles instanceof String) // Surfaces the failed library by name so operators can grep startup logs. // See dotCMS issue #35601 — previously this was a generic WARN that // produced silent macro-rendering failures after pod restarts. - Logger.error(this.getClass(), + Logger.error(this, "Velocimacro : VM library not found : " + lib + " : " + rnse.getMessage()); failedLibraries.add(lib); diff --git a/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java index ef5306beb7c7..66e8870e3c5a 100644 --- a/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java +++ b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java @@ -15,6 +15,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** @@ -86,6 +87,10 @@ public void initDoesNotThrowByDefault() { // The flag default (false) flows through the configMock's default thenAnswer. final VelocimacroFactory factory = new VelocimacroFactory(rsvc); factory.initVelocimacro(); + + // Verify the library-loading branch actually ran — guards against future + // refactors that might short-circuit before the load loop is reached. + verify(rsvc).getTemplate(MISSING_LIBRARY); } @Test @@ -95,5 +100,7 @@ public void initDoesNotThrowWhenFlagIsExplicitlyFalse() { final VelocimacroFactory factory = new VelocimacroFactory(rsvc); factory.initVelocimacro(); + + verify(rsvc).getTemplate(MISSING_LIBRARY); } } From a0f36b75d5a1d84f0f3d40aa668423067d09c336 Mon Sep 17 00:00:00 2001 From: "daniel.solis" Date: Thu, 21 May 2026 14:01:31 -0600 Subject: [PATCH 5/5] style(velocity): document terminal-throw contract + pin flag-name in test (#35601) Addresses PR #35768 review feedback: - Add a comment at the throw site noting that this exception is terminal: the factory ends in a partial state (permission setup, namespace usage, and autoreload never run) and MUST be discarded. Documents the current contract so a future caller does not catch VelocityException and try to reuse the instance. - Add an assertion in initThrowsWhenFlagIsTrue that the exception message contains FLAG_KEY, pinning the actionable opt-out hint so a future refactor cannot silently drop it. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../java/org/apache/velocity/runtime/VelocimacroFactory.java | 5 +++++ .../org/apache/velocity/runtime/VelocimacroFactoryTest.java | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java index 9687d8f54eb4..28419b49bd0a 100644 --- a/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java +++ b/dotCMS/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java @@ -245,6 +245,11 @@ else if (libfiles instanceof String) if (failOnMissing && !failedLibraries.isEmpty()) { + // Terminal: this throw short-circuits the remainder of initVelocimacro() + // (permission setup, namespace usage, autoreload). The factory ends up in + // a partial state and MUST be discarded — do not catch this and reuse the + // instance. The documented call path is VelocityUtil.getEngine() → + // DotRuntimeException → InitServlet aborts → pod never becomes ready. throw new VelocityException( "Velocimacro : required VM libraries failed to load: " + failedLibraries diff --git a/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java index 66e8870e3c5a..76a7be3c4dff 100644 --- a/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java +++ b/dotCMS/src/test/java/org/apache/velocity/runtime/VelocimacroFactoryTest.java @@ -78,6 +78,11 @@ public void initThrowsWhenFlagIsTrue() { assertTrue( "Exception message should name the failed library: " + expected.getMessage(), expected.getMessage().contains(MISSING_LIBRARY)); + // Pin the actionable opt-out hint so a future refactor cannot silently + // drop it from the exception message. + assertTrue( + "Exception message should reference the opt-out flag: " + expected.getMessage(), + expected.getMessage().contains(FLAG_KEY)); } }