Tiles accessors are called by OGNL internal machinery with raw OgnlContext,
not within a Struts evaluation context, so they cannot use StrutsContext.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Detect Hibernate availability once at class-load time via Class.forName()
and short-circuit all Hibernate-related methods immediately when absent.
This eliminates repeated LinkageError/NoClassDefFoundError exceptions
that cause significant performance degradation in applications without
Hibernate on the classpath.
Fixes https://issues.apache.org/jira/browse/WW-5622
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Modern JDKs (7u45+) already protect against this attack with a built-in
64K entity expansion limit. These changes add defense-in-depth hardening
and remove unnecessary attack surface.
- Remove unused parseStringAsXML feature from StringAdapter to eliminate
a theoretical XML Entity Expansion vector
- Deprecate setParseStringAsXML() and getParseStringAsXML() for removal
- Enable SECURE_PROCESSING feature in DigesterDefinitionsReader
- Add unit test verifying JDK's entity expansion limit rejects
Billion Laughs payloads
- Add research document with vulnerability analysis
Co-authored-by: Claude <noreply@anthropic.com>
* WW-5537 Add InternalDestroyable and ContextAwareDestroyable interfaces
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 ContainerHolder: ThreadLocal with AtomicLong generation counter
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 FinalizableReferenceQueue: volatile instance, join, classloader null
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 ScopeInterceptor.clearLocks: add synchronized block
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 CompoundRootAccessor, DefaultFileManager: implement InternalDestroyable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Add InternalDestroyable adapter classes for static cache cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Register InternalDestroyable beans in struts-beans.xml
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 JSON plugin: add JSONCacheDestroyable for BeanInfo cache cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Dispatcher.cleanup: refactor into focused methods with InternalDestroyable discovery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Rewrite DispatcherCleanupTest for InternalDestroyable discovery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Add log4j-web for proper Log4j2 lifecycle in Servlet container
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Dispatcher.destroyObjectFactory: add early return on null, use pattern matching
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Fix @since annotations: 7.1.0 -> 7.2.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Add Container.destroy() to clear internal caches on undeploy
Container now exposes a destroy() method that clears factories, injectors,
constructors, and ThreadLocals. This releases Class<?> keys and JDK
DelegatingClassLoader instances that pin the webapp classloader.
DefaultConfiguration.destroy() calls container.destroy() and
reloadContainer() delegates to destroy() to avoid duplication.
Also fixes JSONCacheDestroyable referencing non-existent DefaultJSONWriter
(renamed to StrutsJSONWriter).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Fix Container.destroy(): don't clear factories, don't call from reloadContainer
factories must remain intact because existing code holds direct
references to the Container after destroyConfiguration() and expects
it to still resolve dependencies (e.g. during configuration reload).
reloadContainer() reverted to clearing packageContexts/loadedFileNames
directly — calling destroy() there nulled the container reference and
cleared state needed during the bootstrap transition.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Restore destroy() call in reloadContainer()
The test failures were caused by factories.clear() in
Container.destroy(), not by calling destroy() from reloadContainer().
Now that factories.clear() is removed, destroy() is safe to call
here — it clears packageContexts, loadedFileNames, and the container's
reflection caches in one place.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* WW-5537 Fix Sonar issues: thread-safe FinalizableReferenceQueue, empty method comments
- Replace volatile field with AtomicReference in FinalizableReferenceQueue
for proper thread safety using getAndSet()
- Add comments to empty destroy() implementations in test mocks
- Replace deprecated new URL() with URI.toURL() in DispatcherCleanupTest
- Add comments to empty listener methods in DispatcherCleanupTest
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add configurable limits to the JSON plugin to prevent denial-of-service
attacks via malicious payloads (deeply nested objects, huge arrays, long
strings).
Changes:
- Extract JSONReader interface from class, create StrutsJSONReader impl
with maxElements, maxDepth, maxStringLength, maxKeyLength enforcement
- Rename DefaultJSONWriter to StrutsJSONWriter (Struts* naming convention)
- Add JSONBeanSelectionProvider for bean aliasing via constants
- Update JSONUtil with @Inject for reader/writer, add instance
deserializeInput() with maxLength check, deprecate static deserialize()
- Wire limits into JSONInterceptor with @Inject from constants
- Register beans and defaults in struts-plugin.xml
Default limits: 10K elements, 64 depth, 2MB length, 256KB strings, 512 keys.
All configurable via struts.xml constants or per-action interceptor params.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add production deployment warnings to showcase and rest-showcase READMEs
- Convert README.txt to README.md with proper Markdown formatting
- Restrict ViewSourceAction config parameter to XML files within webapp path
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace e.printStackTrace() with System.err.println() to properly
log errors to stderr without stack trace noise in CLI tools.
Issue: JRException handling in JasperReports compilation
Co-authored-by: Senrian <sen@senrian.com>
* WW-2963 fix(core): resolve default-action-ref via wildcard matching
When default-action-ref names an action that only exists as a wildcard
pattern (e.g., "movie-list" matching "movie-*"), the fallback now tries
wildcard matching after the exact map lookup fails. This mirrors the
exact→wildcard resolution already used for request action names.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* WW-2963 refactor(core): reduce cognitive complexity of findActionConfigInNamespace
Extract default-action-ref resolution into findDefaultActionConfig() and
replace the deeply nested if-pyramid with early returns, reducing the
nesting depth from 5 to 1 to satisfy Sonar's complexity threshold.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Made-with: Cursor
---------
Co-authored-by: Claude <noreply@anthropic.com>
- Add serialization support for LocalDate, LocalDateTime, LocalTime,
ZonedDateTime, OffsetDateTime, and Instant in DefaultJSONWriter
- Add deserialization support for the same types in JSONPopulator
- Support @JSON(format=...) custom formats for all temporal types
- Fix Instant custom-format serialization requiring UTC zone
- Add Calendar serialization/deserialization via temporal bridge
- Add comprehensive tests for all temporal types including custom
formats, malformed input, and null handling
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Moved to user-wide ~/.claude/agents/ to make it available across all
projects. The agent is now project-agnostic and auto-detects build tools.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(i18n): ensure request_locale takes precedence over Accept-Language when supportedLocale is configured
When supportedLocale was configured on the I18nInterceptor, the Accept-Language
header match in AcceptLanguageLocaleHandler.find() returned early before
SessionLocaleHandler/CookieLocaleHandler ever checked their explicit locale
parameters (request_locale, request_cookie_locale). This made it impossible
to switch locale via request parameters when supportedLocale was set.
Changes:
- Reorder AcceptLanguageLocaleHandler.find() to check request_only_locale
before Accept-Language matching
- Reorder SessionLocaleHandler.find() to check request_locale before super
- Reorder CookieLocaleHandler.find() to check request_cookie_locale before super
- Add isLocaleSupported() helper to validate locales against supportedLocale
- Filter all locale sources (params, session, cookies) through supportedLocale
- Add 4 tests covering the bug scenario and supportedLocale filtering
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test(i18n): cover missing supportedLocale locale-selection paths
Add regression tests for unsupported request_cookie_locale fallback, stored cookie revalidation, and request_only_locale precedence to lock in WW-5549 behavior across remaining branches.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(i18n): extract locale handlers with deprecated inner wrappers
Move locale handler implementations into a dedicated interceptor.i18n package with reusable abstract bases, keep thin deprecated inner wrappers in I18nInterceptor for one release-cycle compatibility, and document the LocaleHandler contract.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(i18n): validate request_only_locale against supportedLocale and fix Accept-Language fallback
RequestLocaleHandler.find() now checks isLocaleSupported() before
returning, preventing unsupported locales from slipping through via
the request_only_locale parameter. AcceptLanguageLocaleHandler.find()
now returns the first Accept-Language locale when supportedLocale is
empty, fixing ACCEPT_LANGUAGE storage mode with no filter configured.
Also includes refactoring: deprecated inner classes collapsed with
LocaleHandlerAdapter, shouldStore field encapsulated via disableStore(),
logger pattern standardized to private static final, and class-level
JavaDoc added to handler classes.
Made-with: Cursor
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Apply recommendations from CLAUDE.md benchmarking study (1,188 tests
across 3 models): remove redundant generic instructions, reframe
prohibitions as positive directives, trim inferable content, and
keep only project-specific knowledge that Claude cannot derive from
the codebase itself. Reduces file from 142 to 64 lines.
Key changes:
- Remove Common Pitfalls (negative framing, generic, duplicated)
- Remove Available Tools section (redundant with system prompt)
- Trim build commands to project-specific flags only
- Collapse Technology Stack into one-line overview
- Reframe security directives from "never do X" to "do Y instead"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
DefaultActionProxy.resolveMethod() unconditionally set methodSpecified=false
when the method was not passed explicitly, including when it was resolved from
ActionConfig (e.g., wildcard substitution like method="{1}"). This caused
HttpMethodInterceptor to skip method-level annotation checks for wildcard
actions, falling back to class-level annotations instead.
Move methodSpecified=false inside the inner branch that defaults to "execute",
so config-resolved methods (including wildcard-substituted ones) correctly
report isMethodSpecified()=true. Update Javadoc to reflect the corrected
semantics.
Fixes [WW-5535](https://issues.apache.org/jira/browse/WW-5535)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>
* feat(proxy): WW-5514 add StrutsProxyService for proxy detection and resolution
Introduces a configurable ProxyService interface and StrutsProxyService
implementation for detecting and resolving Spring AOP/Hibernate proxies.
Key changes:
- Add ProxyService interface with isProxy, ultimateTargetClass, and
resolveTargetMember methods
- Add StrutsProxyService implementation using configurable caches
- Add ProxyCacheFactory and StrutsProxyCacheFactory for cache management
- Integrate ProxyService into ChainingInterceptor, ParametersInterceptor,
and SecurityMemberAccess
- Add integration test with Spring AOP proxied action chaining
- Add configuration constants for proxy cache type and size
The StrutsProxyService correctly handles:
- Spring CGLIB proxies (class-based)
- Spring JDK dynamic proxies (interface-based)
- Hibernate entity proxies
- Member resolution for allowlist checking
Fixes WW-5514
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* test(proxy): WW-5514 add ProxyService integration tests for Spring proxies
Add integration tests to SpringProxyUtilTest that verify the new
ProxyService works correctly with real Spring AOP proxies, alongside
the existing deprecated ProxyUtil tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(proxy): WW-5514 address PR review feedback for proxy caches
Remove targetClassCache from StrutsProxyService to avoid memory leak
(object-keyed cache reintroduced from PR #1578). Change default proxy
cache type to wtlfu to align with all other caches. Switch deprecated
ProxyUtil static caches to BASIC to remove hard Caffeine dependency.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The duplicate @Action name detection in PackageBasedActionConfigBuilder
was embedded inside a conditional block that only ran when execute() was
NOT annotated with @Action. This meant two methods could map to the same
action name silently when execute() had an @Action annotation, with one
overwriting the other non-deterministically.
Extract the duplicate check to run unconditionally before the conditional
block, so it applies to all annotated methods regardless of whether
execute() is annotated.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude <noreply@anthropic.com>