Skip to content

WW-5668 Bound the localized-text provider caches and align request-locale resolution - #1821

Open
lukaszlenart wants to merge 11 commits into
mainfrom
WW-5668-i18n-cache-bounds
Open

WW-5668 Bound the localized-text provider caches and align request-locale resolution#1821
lukaszlenart wants to merge 11 commits into
mainfrom
WW-5668-i18n-cache-bounds

Conversation

@lukaszlenart

Copy link
Copy Markdown
Member

Fixes WW-5668

Follow-up to WW-5540. Two related, independent changes to the localized-text subsystem.

1. Bounded localized-text caches

AbstractLocalizedTextProvider kept its five internal caches (bundlesMap, messageFormats, missingBundles, and the classHierarchyCache / packageHierarchyCache added in WW-5540) as plain ConcurrentHashMaps with no configurable upper bound or eviction. The rest of the framework already standardises on bounded caches via OgnlCacheFactory (struts.ognl.expressionCacheMaxSize, struts.proxy.cacheMaxSize, both wtlfu); these were the outlier.

  • Reuse the existing OgnlCache abstraction (DefaultOgnlCacheFactory) for all five caches, with configurable size and eviction.
  • New constants struts.i18n.cacheType (default wtlfu) and struts.i18n.cacheMaxSize (default 10000), documented in default.properties.
  • Added remove(key) to OgnlCache (needed by clearBundle).
  • Caches are transient and rebuilt in readObject so the provider stays serializable; a dedicated lock replaces synchronizing on the now-reassignable bundlesMap.

All five caches are pure/reconstructible, so eviction can only cause a recompute, never a wrong or stale localized result.

2. Consistent request-locale resolution (opt-in)

I18nInterceptor validates a resolved locale against the available-locale set (LocaleProvider.isValidLocale), but Dispatcher.getLocale(...) (used when struts.locale is unset) returns request.getLocale() directly without that check.

  • New struts.locale.validateRequestLocale (default false — current behaviour preserved byte-for-byte). When enabled, Dispatcher resolves request-derived locales against the JVM available-locale set, falling back to the configured struts.locale (else the JVM default), consistent with I18nInterceptor.

Testing

  • New tests: cache bound invariant, correctness under eviction, reload clears, serialize→deserialize→findText, cache-type selection, and the three Dispatcher flag states (off / on-available / on-fallback).
  • Full core suite green.

Backward compatibility

  • Part 1 changes internal cache implementations only; eviction is correctness-safe; defaults match existing cache conventions.
  • Part 2 is fully opt-in and defaults off.

🤖 Generated with Claude Code

lukaszlenart and others added 9 commits July 30, 2026 12:59
…resolution

Follow-up to WW-5540. Bound the AbstractLocalizedTextProvider caches via the
existing OgnlCache abstraction (configurable struts.i18n.cacheType/cacheMaxSize),
and add opt-in request-locale resolution consistency between Dispatcher and
I18nInterceptor (struts.locale.validateRequestLocale, default off).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-locale resolution

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ields

synchronized (bundlesMap) locked on a monitor that rebuildI18nCaches()
can reassign; introduce a dedicated bundlesMapLock and lock on that
instead. Mark the five i18n cache fields volatile for safe publication
across the reassignment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s serializable

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the one-line Javadoc that sibling fields/setters carry to
validateRequestLocale and its @Inject setter in Dispatcher, and add two
tests covering StrutsLocalizedTextProvider's serialize/deserialize cache
rebuild and cacheType selection behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lukaszlenart
lukaszlenart force-pushed the WW-5668-i18n-cache-bounds branch from e02aac2 to 089d9c3 Compare July 30, 2026 11:00
lukaszlenart and others added 2 commits July 30, 2026 13:21
…aches

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…le tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI 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.

Pull request overview

This PR updates Struts’ localized-text (i18n) subsystem to (1) bound and standardize the internal localized-text provider caches using the existing OgnlCache abstraction, and (2) optionally make request-locale resolution in Dispatcher consistent with I18nInterceptor by validating request-derived locales against the JVM available-locale set.

Changes:

  • Replaced five unbounded ConcurrentHashMap-based caches in AbstractLocalizedTextProvider with configurable, bounded OgnlCache instances (struts.i18n.cacheType, struts.i18n.cacheMaxSize) and ensured caches rebuild after deserialization.
  • Added an additive remove(K) operation to OgnlCache and implemented it across cache implementations to support existing bundle-clearing behavior.
  • Added struts.locale.validateRequestLocale (default false) and routed Dispatcher’s request-locale selection through a helper that can validate/fallback when enabled, plus accompanying tests.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
docs/superpowers/specs/2026-07-30-WW-5668-i18n-cache-bounds-locale-resolution-design.md New design spec documenting bounded i18n caches and opt-in request-locale validation behavior.
docs/superpowers/plans/2026-07-30-WW-5668-i18n-cache-bounds-locale-resolution.md New implementation plan detailing the steps, constants, and tests for the change set.
core/src/main/java/org/apache/struts2/ognl/OgnlCache.java Adds remove(K) to the cache abstraction to support bundle cache entry removal.
core/src/main/java/org/apache/struts2/ognl/OgnlCaffeineCache.java Implements remove(K) for the Caffeine-backed cache.
core/src/main/java/org/apache/struts2/ognl/OgnlDefaultCache.java Implements remove(K) for the default map-backed cache.
core/src/main/java/org/apache/struts2/ognl/OgnlLRUCache.java Implements remove(K) for the LRU cache.
core/src/test/java/org/apache/struts2/ognl/OgnlCacheRemoveTest.java New unit test validating remove(K) behavior across cache implementations.
core/src/main/java/org/apache/struts2/StrutsConstants.java Adds new constants for i18n cache config and request-locale validation flag.
core/src/main/resources/org/apache/struts2/default.properties Documents new configuration keys and defaults for i18n caches and locale validation.
core/src/main/java/org/apache/struts2/text/AbstractLocalizedTextProvider.java Converts internal i18n caches to bounded OgnlCache instances; adds rebuild/readObject logic and cache size accessors for tests.
core/src/main/java/org/apache/struts2/text/StrutsLocalizedTextProvider.java Adds serialVersionUID to align with serialization changes/expectations.
core/src/main/java/org/apache/struts2/text/GlobalLocalizedTextProvider.java Adds serialVersionUID to align with serialization changes/expectations.
core/src/test/java/org/apache/struts2/text/StrutsLocalizedTextProviderTest.java Adds tests for cache bounding, eviction correctness, reload clearing, deserialization usability, and cache-type selection.
core/src/main/java/org/apache/struts2/dispatcher/Dispatcher.java Adds validateRequestLocale flag and resolveRequestLocale() helper; routes getLocale() request-locale retrieval through the helper.
core/src/test/java/org/apache/struts2/dispatcher/DispatcherTest.java Adds tests for the new request-locale validation flag (off/on/invalid fallback behavior).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 948 to 949
LOG.warn(new ParameterizedMessage("Cannot convert 'struts.locale' = [{}] to proper locale, defaulting to request locale [{}]",
defaultLocale, locale), e);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants