* WW-5666 fix(json): apply the input length limit while reading
The configured JSON input length limit was evaluated after accumulating each
line of input. It is now evaluated as the input is read, in fixed-size chunks,
so enforcement no longer varies with the structure of the input.
Line terminators are no longer stripped while reading. They are insignificant
whitespace between tokens, but an unescaped control character inside a string
value is now preserved rather than silently removed.
* WW-5666 fix(core): bound the CSP report body read and make the limit configurable
CspReportAction read the submitted report body with a single readLine() and had
no limit of its own. Read it up to a limit instead, defaulting to 8192
characters and configurable through struts.csp.report.maxSize. A body above the
limit is discarded with a warning rather than processed.
The limit is injected when the action is built, before the interceptor stack
runs, because withServletRequest is invoked by the servletConfig interceptor
ahead of staticParams and params. Values that are not usable as a buffer size
are ignored with a warning.
* WW-4858 fix(json): evaluate name allowlist at leaf keys only
The JSON population filter walked the object tree and applied every name
check at every node before recursing. Accepted name patterns and the
ParameterNameAware callback target the full dotted binding path, so gating
an intermediate node (e.g. "bean") against a leaf-specific rule dropped the
entire subtree before the leaf ("bean.stringField") was ever evaluated —
diverging from ParametersInterceptor, which only evaluates complete leaf
names. For arrays it also meant the accepted allowlist judged the container
name instead of the element path.
Split the per-key gate: length, excluded patterns, @StrutsParameter
authorization and property filters stay per-node (exclusion is prefix-safe
and authorization is intentionally hierarchical); accepted patterns and
ParameterNameAware move to leaf keys only, including scalar array elements
at their indexed path ("items[0]"). This reproduces the flat-path semantics
exactly. Excluded/include-property behavior is unchanged.
Tests: nested-object leaf populates under a leaf-targeting accepted pattern
and a ParameterNameAware action that rejects the intermediate node; accepted
patterns now apply to the array element path; nested include-property
filtering still works.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 fix(json): apply per-node checks to scalar array elements
Scalar list elements were gated only by the leaf name-allowlist and value
checks, skipping the per-node checks (length, excluded patterns,
@StrutsParameter authorization, property filters). That left the JSON path
more permissive than ParametersInterceptor, which evaluates all of these
against the full indexed name "items[0]".
Apply isAcceptableNode(elementPrefix, ...) to scalar list elements so an
element is gated exactly as the flat path gates "items[0]". Note this makes
scalar-list @StrutsParameter authorization use the element path (depth 1,
read method) rather than only the container (depth 0), matching the flat
path.
Tests: excluded name pattern and @StrutsParameter authorization now apply at
the list element path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 refactor(json): extract keyTypeName helper to lower cognitive complexity
Move the non-String-key logging ternary out of filterUnacceptableKeysRecursive
into a keyTypeName helper. Pure extraction, no behavior change; drops the
method's cognitive complexity from 17 to 14, under Sonar's S3776 threshold.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5650 revert StrutsJSONReader to plain single-use instance fields
* WW-5650 revert StrutsJSONWriter to plain single-use instance fields
* WW-5650 obtain a fresh JSONUtil per request in JSONInterceptor
* WW-5650 resolve JSONUtil lazily only on JSON request paths
Move getJSONUtil() into the JSON and JSON-RPC branches of intercept() so
requests with a non-JSON content type no longer construct and discard an
unused JSONUtil/reader/writer graph. Also trim a stray trailing blank line
in StrutsJSONWriter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5650 test(json): assert JSONWriter bean stays prototype-scoped
Guards the response-side invariant from WW-5644: StrutsJSONWriter now uses
plain instance fields and is not thread-safe, so cross-request safety relies
solely on the writer bean being prototype-scoped. Assert distinct instances
per container lookup so a future switch to singleton scope fails the build.
Addresses review feedback on #1782 without adding a getWriter() accessor
purely for tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add two tests to JSONInterceptorTest exercising the nested-object path for
the name/value filtering added in WW-4858:
- testAcceptedNamePatternRejectsNestedKey: accepted name patterns are raw
full-match regexes with no hierarchy expansion, so the intermediate node
("bean") must itself match an accepted pattern or the whole subtree is
dropped before the leaf is visited.
- testIncludePropertiesAppliedToNestedInputWhenEnabled: include patterns do
expand across the hierarchy, so "bean.stringField" also matches the
intermediate "bean" and the nested leaf populates while the excluded
sibling "bean.intField" is dropped.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(json): confine StrutsJSONWriter write state to the writing thread
JSONUtil obtains its JSONWriter once via @Inject and reuses that same
instance across every concurrent response handled by that JSONResult/
JSONInterceptor configuration. StrutsJSONWriter kept its output buffer,
cyclic-reference stack, root object, and expression-path state
(buf/stack/root/buildExpr/exprStack/excludeProperties/
includeProperties/excludeNullProperties) as plain instance fields, all
reset in place at the start of write().
Two concurrent write() calls on the same instance therefore race on
that reset: one call's in-progress buffer can be wiped and overwritten
by a second, unrelated concurrent call before the first call reads it
back via buf.toString(), so one request's serialized JSON can be
returned as a completely different, concurrently-served request's
response body.
Move buf/stack/root/buildExpr/exprStack/excludeProperties/
includeProperties/excludeNullProperties into a WriteState confined to
a ThreadLocal, scoped to a single write() call. Method signatures and
behavior are otherwise unchanged so existing StrutsJSONWriter
subclasses keep working; ignoreHierarchy/dateFormat/enumAsBean/
excludeProxyProperties stay as plain instance fields since they are
set to the same value on every call for a given writer configuration
and are safe to share.
* test(json): raise writer concurrency test to 16 threads for reliable repro
Verified independently that the 2-thread version can miss the race on
machines with more cores than contending threads (with no CPU
contention, the OS scheduler has no need to preempt either thread
mid-call, so the corruption window is rarely hit): 0 reproductions in
8 reruns against unpatched code on a 10-core machine. Sixteen threads
reproduced it reliably (44,646/320,000 corrupted responses against
unpatched StrutsJSONWriter), and confirmed zero corruption against the
fix under the same load.
---------
Co-authored-by: g0w6y <g0w6y@users.noreply.github.com>
* fix(json): confine StrutsJSONReader parse state to the parsing thread
JSONInterceptor obtains its JSONReader once via @Inject and reuses that
same instance across every concurrent request handled by that
interceptor. StrutsJSONReader kept its parse cursor, token buffer and
nesting-depth counter (used to enforce maxDepth/maxElements/
maxStringLength/maxKeyLength) as plain instance fields, so two
concurrent read() calls on the same instance tore each other's state:
one request's depth counter could be decremented by an unrelated
concurrent request finishing its own parse, letting payloads deeper
than the configured maxDepth through, and the shared character cursor
and string/number buffer let fragments of one request's JSON body leak
into a different, concurrently-parsed request's result.
Move the cursor, current character, token, buffer and depth into a
ParseState confined to a ThreadLocal, scoped to a single read() call.
Method signatures and behavior are otherwise unchanged so existing
StrutsJSONReader subclasses keep working; the limit fields
(maxElements/maxDepth/maxStringLength/maxKeyLength) stay as plain
instance fields since they are set to the same value on every call for
a given interceptor configuration and are safe to share.
* test(json): raise reader concurrency test to 16 threads for reliable repro
Verified independently that the 2-thread version can miss the race on
machines with more cores than contending threads (with no CPU
contention, the OS scheduler has no need to preempt either thread
mid-call, so the corruption window is rarely hit): 0 reproductions in
8 reruns against unpatched code on a 10-core machine. Sixteen threads
reproduced both symptoms reliably against unpatched StrutsJSONReader
(81 cross-thread data leaks and 79 maxDepth bypasses out of 160,000
attempts), and confirmed zero of either against the fix under the
same load. Combined the two prior tests into one, since both symptoms
come from the same shared parse state and are naturally checked
together per thread.
---------
Co-authored-by: g0w6y <g0w6y@users.noreply.github.com>
* WW-4858 docs(json): design for honoring parameter filtering during JSON population
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 docs(json): implementation plan for JSON parameter filtering
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 feat(json): enforce excluded/accepted name patterns on JSON population
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 feat(json): enforce param-name max length on JSON population
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 feat(json): honor ParameterNameAware and ParameterValueAware on JSON population
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 feat(json): add opt-in excluded/accepted value patterns on JSON population
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 feat(json): opt-in applying excludeProperties/includeProperties to JSON input
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-4858 test(json): cover nested and list-element paths; clarify filter comments
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5641 docs: design spec for JSON writer/reader override regression
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5641 docs: implementation plan for JSON writer/reader override fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5641 fix: run JSON bean-selection from struts-deferred.xml
The JSON plugin declared <bean-selection> in struts-plugin.xml, which runs
at plugin-parse time, before the application struts.xml is folded in. That
froze the JSONWriter/JSONReader default binding to StrutsJSONWriter/Reader,
so struts.json.writer / struts.json.reader overrides were ignored.
Move the element to struts-deferred.xml, which Dispatcher loads last (after
the app config and core's StrutsBeanSelectionProvider), so the alias honors
the override. Mirrors the velocity plugin. JSONUtil is unchanged from main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* WW-5626 add ParameterAuthorizer#resolveTarget for centralized ModelDriven resolution
Move the ValueStack peek logic that derives the target object from action+ModelDriven
state out of ParametersInterceptor and into ParameterAuthorizer. Callers that need
both authorization and the resolved target (for downstream OGNL allowlisting) can
now call resolveTarget once and reuse the result.
* WW-5626 delegate ModelDriven target resolution to ParameterAuthorizer
Replace the inline ValueStack peek in ParametersInterceptor#isParameterAnnotatedAndAllowlist
with a call to ParameterAuthorizer#resolveTarget. The ModelDriven import is no longer
needed in this class.
* WW-5626 defensively skip non-String JSON keys in authorization filter
The (String) cast in filterUnauthorizedKeysRecursive threw ClassCastException
for any custom JSONReader producing non-String keys. Replace with an instanceof
pattern that debug-logs and skips entries whose key cannot be converted to a
parameter path.
* WW-5626 add real JacksonJsonHandler integration tests for @StrutsParameter filtering
The existing ContentTypeInterceptorTest uses mock ContentTypeHandlers, so its
requireAnnotations=true tests verify only that intercept() returns SUCCESS — they
assert nothing about which properties were actually filtered. These integration
tests use a real JacksonJsonHandler + a real StrutsParameterAuthorizer to verify
end-to-end property-level filtering for top-level annotated/unannotated properties
and nested properties at varying authorized depths.
The SecureRestAction fixture documents a semantic divergence: REST's recursive
copy authorizes each path level independently, so depth-0 authorization on the
top-level property requires @StrutsParameter on the setter even when nested
field access is the actual goal. ParametersInterceptor only requires the getter
annotation. This divergence is tracked for the Approach C refactor.
* WW-5626 make ParameterAuthorizer#resolveTarget a default method to preserve SAM
Making resolveTarget abstract broke ParameterAuthorizer as a functional interface,
which the existing JSON and REST plugin tests rely on for lambda-based stubs:
interceptor.setParameterAuthorizer((parameterName, target, action) -> true);
The default returns the action unchanged — adequate for lambda test stubs whose
authorization decisions don't depend on the resolved target. The production
implementation (StrutsParameterAuthorizer) overrides this with the proper
ModelDriven value-stack peek.
* WW-5624 fix(security): enforce @StrutsParameter on JSON/REST body deserialization
Extract ParameterAuthorizer service from ParametersInterceptor to share
@StrutsParameter annotation enforcement across all input channels.
The json-plugin (JSONInterceptor) and rest-plugin (ContentTypeInterceptor)
previously bypassed @StrutsParameter checks when deserializing request
bodies, allowing mass assignment even when
struts.parameters.requireAnnotations=true.
Changes:
- New ParameterAuthorizer interface and DefaultParameterAuthorizer impl
- JSONInterceptor: filter unauthorized Map keys before populateObject()
- ContentTypeInterceptor: two-phase deserialization (fresh instance then
copy authorized properties) when requireAnnotations=true; direct
deserialization for backward compat when disabled
- OGNL ThreadAllowlist side effects remain in ParametersInterceptor only
- Full DI wiring: struts-beans.xml + StrutsBeanSelectionProvider +
DefaultConfiguration
- 15 new unit tests for ParameterAuthorizer, 2 for JSON plugin,
2 for REST plugin; 32 existing regression tests verified
* WW-5624 address review feedback from lukaszlenart on PR #1657
1. Rename DefaultParameterAuthorizer → StrutsParameterAuthorizer
per Struts naming convention (inline suggestion)
2. Narrow ModelDriven exemption: require action instanceof ModelDriven
before exempting target from @StrutsParameter checks. Prevents
non-ModelDriven root objects (e.g. JSONInterceptor.root) from
bypassing annotation enforcement.
3. Recursive JSON key filtering: filterUnauthorizedKeys() now recurses
into nested Maps and Lists, building dot-notation paths (e.g.
"address.city") for path-aware @StrutsParameter(depth=N) checks.
4. Deep REST property copy: copyAuthorizedProperties() now recurses
into nested bean types with path-aware authorization. Collections,
Maps, primitives, and java.lang/java.time types are copied directly.
5. Null-skip semantics preserved and documented: in two-phase
deserialization, null in freshInstance is indistinguishable from
"not present in request" — clearing would destroy pre-initialized
fields. Kept as intentional design choice with inline documentation.
6. No-arg constructor fallback: when target class lacks a no-arg
constructor, falls back to single-phase deserialization with
post-scrub of unauthorized properties, preserving backward compat.
7. New regression tests:
- Non-ModelDriven target with different object (must not exempt)
- Nested JSON keys recursively filtered
- Non-action root object still checked by authorizer
All 280+ core tests, 124 JSON tests, 76 REST tests pass with 0 regressions.
* WW-5624: v3 — fix indexed-path depth parity with ParametersInterceptor
Four gaps identified by lukaszlenart's April 10 review are now fully addressed:
1. JSON filterUnauthorizedList: pass prefix+"[0]" instead of bare prefix so
that list element properties gain one extra '[' in their path — e.g.
"publicPojoListDepthOne[0].key" (depth=2) is now correctly rejected when
@StrutsParameter(depth=1), matching ParametersInterceptor semantics.
Also recurse into nested List<List<Map>> via an else-if branch.
2. REST copyAuthorizedProperties: add authTarget parameter (always = root
action/model, passed unchanged through all recursion levels).
isAuthorized() now checks the full path against the root class, so
"address.city" is looked up on the action, not on the Address object.
3. REST Collection/Map/array deep authorization: replaced the as-is copy
with deepCopyAuthorizedCollection(), deepCopyAuthorizedMap(), and
deepCopyAuthorizedArray() helpers — each iterates elements with
path+"[0]" prefix, authorizing every complex element individually.
No-arg fallback skips the element rather than copying an unfiltered
object graph (security fix over plan's original as-is suggestion).
4. REST scrubUnauthorizedProperties: now fully recursive via
scrubUnauthorizedPropertiesRecursive() — visits nested beans,
collection elements, and map values with authTarget always pointing
to the root. Includes identity-based visited-set to guard against
circular reference cycles.
Tests: core 2920 + json 124 + rest 76 = 3120, 0 failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* WW-5624: v3.1 — fix collection type, identity set, isNestedBeanType coverage
Three correctness/security issues identified by independent review:
1. deepCopyAuthorizedCollection/deepCopyAuthorizedMap type preservation:
Previously always returned ArrayList/LinkedHashMap. If the action field
is typed Set<Pojo> or SortedMap<K,V>, writeMethod.invoke would throw
IllegalArgumentException. Now: SortedSet→TreeSet, Set→LinkedHashSet,
List→ArrayList; SortedMap→TreeMap, Map→LinkedHashMap.
2. scrubUnauthorizedPropertiesRecursive visited-set identity safety:
Replaced Set<Integer>+System.identityHashCode (not collision-safe) with
Collections.newSetFromMap(new IdentityHashMap<>()) which uses reference
equality (==). A hash collision could have caused a valid nested object
to be skipped, leaving unauthorized properties un-scrubbed.
3. isNestedBeanType now excludes all standard-library leaf packages:
java.util.* non-Collection/Map types (UUID, Currency, Locale, Date),
java.time.* (all temporal types, not just Temporal subinterface),
java.net.*, java.io.*, java.nio.*. Previously UUID etc. would return
true, causing the code to recurse into their internal fields and silently
drop the value when no @StrutsParameter annotation matched.
Tests: json 124 + rest 76 = 200, 0 failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* WW-5624: v4 — close bulk-copy fallback, reject body when no no-arg ctor
Two remaining gaps addressed per lukaszlenart's April 11 review:
1. copyAuthorizedProperties bulk-copy fallback removed:
When a nested target bean is null and createFreshInstance fails (no
no-arg constructor), the previous code fell back to
writeMethod.invoke(target, sourceValue) — copying the whole nested
object graph without per-path authorization. Now logs a warning and
skips the property entirely (same policy as deepCopyAuthorizedCollection
elements with no no-arg constructor).
2. Top-level no-arg constructor fallback changed from scrub to reject:
When requireAnnotations=true and the target class has no no-arg
constructor, body deserialization is now rejected entirely
(handler.toObject is never called). The previous best-effort scrub
path could not guarantee that all nested unauthorized properties were
nulled out. scrubUnauthorizedProperties and its recursive helper are
removed as dead code.
Tests: rest 76, 0 failures.
---------
Co-authored-by: tranquac <tranquac@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <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 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>
* 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>
* [maven-release-plugin] prepare release STRUTS_7_1_1
* [maven-release-plugin] rollback the release of STRUTS_7_1_1
* [maven-release-plugin] prepare release STRUTS_7_1_1
* [maven-release-plugin] prepare for next development iteration