mirror of
https://github.com/apache/struts.git
synced 2026-08-05 14:47:09 +00:00
WW-5326 docs: add Jira reference to spec and plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
# Hibernate Proxy Detection Optimization
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Eliminate `LinkageError` exceptions thrown when Hibernate is not on the classpath by detecting availability once at class-load time.
|
||||
|
||||
**Architecture:** Add a static availability check in `StrutsProxyService` that probes for `org.hibernate.proxy.HibernateProxy` once during class initialization. All Hibernate-related methods short-circuit immediately when Hibernate is absent. Same pattern applied to deprecated `ProxyUtil`.
|
||||
|
||||
**Tech Stack:** Java 17, JUnit 5, AssertJ, Mockito
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Hibernate Availability Check to StrutsProxyService
|
||||
|
||||
**Files:**
|
||||
- Modify: `core/src/main/java/org/apache/struts2/util/StrutsProxyService.java`
|
||||
- Test: `core/src/test/java/org/apache/struts2/util/StrutsProxyServiceTest.java`
|
||||
|
||||
- [ ] **Step 1: Write the failing test — verify no LinkageError is thrown when Hibernate classes are used**
|
||||
|
||||
The existing tests already call `isHibernateProxy()` and `isHibernateProxyMember()` with non-Hibernate objects. We need a test that verifies the short-circuit behavior works correctly. Add this test to `StrutsProxyServiceTest.java`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
public void isHibernateProxyDoesNotThrowWhenCalledRepeatedly() {
|
||||
// Verify that calling isHibernateProxy many times for different objects
|
||||
// does not cause performance issues (no exceptions thrown internally)
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
assertThat(proxyService.isHibernateProxy(new Object())).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isHibernateProxyMemberDoesNotThrowWhenCalledRepeatedly() throws NoSuchMethodException {
|
||||
Method method = Object.class.getMethod("toString");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
assertThat(proxyService.isHibernateProxyMember(method)).isFalse();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests to verify they pass (baseline — these pass even without the fix because Hibernate IS on the test classpath)**
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl core -Dtest=StrutsProxyServiceTest#isHibernateProxyDoesNotThrowWhenCalledRepeatedly+isHibernateProxyMemberDoesNotThrowWhenCalledRepeatedly`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 3: Add static Hibernate availability flag to StrutsProxyService**
|
||||
|
||||
In `core/src/main/java/org/apache/struts2/util/StrutsProxyService.java`, add a static availability check at the top of the class and modify the three Hibernate methods to short-circuit:
|
||||
|
||||
```java
|
||||
// Add this field near the top of the class, after the class declaration:
|
||||
private static final boolean HIBERNATE_AVAILABLE = isHibernateAvailable();
|
||||
|
||||
private static boolean isHibernateAvailable() {
|
||||
try {
|
||||
Class.forName("org.hibernate.proxy.HibernateProxy");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then modify the three Hibernate methods to short-circuit:
|
||||
|
||||
**`isHibernateProxy`** — change from:
|
||||
```java
|
||||
@Override
|
||||
public boolean isHibernateProxy(Object object) {
|
||||
try {
|
||||
return object != null && HibernateProxy.class.isAssignableFrom(object.getClass());
|
||||
} catch (LinkageError ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
to:
|
||||
```java
|
||||
@Override
|
||||
public boolean isHibernateProxy(Object object) {
|
||||
if (!HIBERNATE_AVAILABLE || object == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return HibernateProxy.class.isAssignableFrom(object.getClass());
|
||||
} catch (LinkageError ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`isHibernateProxyMember`** — change from:
|
||||
```java
|
||||
@Override
|
||||
public boolean isHibernateProxyMember(Member member) {
|
||||
try {
|
||||
return hasMember(HibernateProxy.class, member);
|
||||
} catch (LinkageError ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
to:
|
||||
```java
|
||||
@Override
|
||||
public boolean isHibernateProxyMember(Member member) {
|
||||
if (!HIBERNATE_AVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return hasMember(HibernateProxy.class, member);
|
||||
} catch (LinkageError ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`getHibernateProxyTarget`** — change from:
|
||||
```java
|
||||
@Override
|
||||
public Object getHibernateProxyTarget(Object object) {
|
||||
try {
|
||||
return Hibernate.unproxy(object);
|
||||
} catch (LinkageError ignored) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
```
|
||||
to:
|
||||
```java
|
||||
@Override
|
||||
public Object getHibernateProxyTarget(Object object) {
|
||||
if (!HIBERNATE_AVAILABLE) {
|
||||
return object;
|
||||
}
|
||||
try {
|
||||
return Hibernate.unproxy(object);
|
||||
} catch (LinkageError ignored) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the full StrutsProxyService test suite**
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl core -Dtest=StrutsProxyServiceTest`
|
||||
Expected: All tests PASS
|
||||
|
||||
- [ ] **Step 5: Run the Spring integration test suite**
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl core -Dtest=StrutsProxyServiceSpringIntegrationTest`
|
||||
Expected: All tests PASS
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add core/src/main/java/org/apache/struts2/util/StrutsProxyService.java core/src/test/java/org/apache/struts2/util/StrutsProxyServiceTest.java
|
||||
git commit -m "WW-5622 Optimize Hibernate proxy detection to avoid LinkageError exceptions
|
||||
|
||||
Add static availability check for Hibernate classes in StrutsProxyService.
|
||||
When Hibernate is not on the classpath, all Hibernate-related methods
|
||||
short-circuit immediately without throwing/catching LinkageError.
|
||||
This eliminates a significant performance penalty for applications
|
||||
that don't use Hibernate."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Apply Same Fix to Deprecated ProxyUtil
|
||||
|
||||
**Files:**
|
||||
- Modify: `core/src/main/java/org/apache/struts2/util/ProxyUtil.java`
|
||||
|
||||
- [ ] **Step 1: Add the same static availability check to ProxyUtil**
|
||||
|
||||
In `core/src/main/java/org/apache/struts2/util/ProxyUtil.java`, add the same pattern:
|
||||
|
||||
```java
|
||||
// Add after the isProxyMemberCache field:
|
||||
private static final boolean HIBERNATE_AVAILABLE = isHibernateAvailable();
|
||||
|
||||
private static boolean isHibernateAvailable() {
|
||||
try {
|
||||
Class.forName("org.hibernate.proxy.HibernateProxy");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then modify the three Hibernate methods in ProxyUtil identically to Task 1:
|
||||
|
||||
**`isHibernateProxy`**:
|
||||
```java
|
||||
@Deprecated(since = "7.2")
|
||||
public static boolean isHibernateProxy(Object object) {
|
||||
if (!HIBERNATE_AVAILABLE || object == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return HibernateProxy.class.isAssignableFrom(object.getClass());
|
||||
} catch (LinkageError ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`isHibernateProxyMember`**:
|
||||
```java
|
||||
@Deprecated(since = "7.2")
|
||||
public static boolean isHibernateProxyMember(Member member) {
|
||||
if (!HIBERNATE_AVAILABLE) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return hasMember(HibernateProxy.class, member);
|
||||
} catch (LinkageError ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`getHibernateProxyTarget`**:
|
||||
```java
|
||||
@Deprecated(since = "7.2")
|
||||
public static Object getHibernateProxyTarget(Object object) {
|
||||
if (!HIBERNATE_AVAILABLE) {
|
||||
return object;
|
||||
}
|
||||
try {
|
||||
return Hibernate.unproxy(object);
|
||||
} catch (LinkageError ignored) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run existing ProxyUtil tests**
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl core -Dtest=ProxyUtilTest`
|
||||
Expected: PASS (or if no dedicated test exists, run the SecurityMemberAccess tests which exercise ProxyUtil indirectly)
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl core -Dtest=SecurityMemberAccessTest`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add core/src/main/java/org/apache/struts2/util/ProxyUtil.java
|
||||
git commit -m "WW-5622 Apply same Hibernate availability optimization to deprecated ProxyUtil"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Run Full Test Suite
|
||||
|
||||
- [ ] **Step 1: Run all core tests**
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl core`
|
||||
Expected: All tests PASS
|
||||
|
||||
- [ ] **Step 2: Run spring plugin tests (exercises proxy detection heavily)**
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl plugins/spring`
|
||||
Expected: All tests PASS
|
||||
|
||||
- [ ] **Step 3: Run json plugin tests (StrutsJSONWriter has Hibernate-related class name checks)**
|
||||
|
||||
Run: `mvn test -DskipAssembly -pl plugins/json`
|
||||
Expected: All tests PASS
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,263 @@
|
||||
# OGNL 3.5.x Upgrade — Design Spec
|
||||
|
||||
> **Jira:** [WW-5326](https://issues.apache.org/jira/browse/WW-5326)
|
||||
|
||||
## Goal
|
||||
|
||||
Upgrade Apache Struts from OGNL 3.4.10 to OGNL 3.5.0-BETA4+ and introduce `StrutsContext extends OgnlContext<StrutsContext>` as the framework's own OGNL evaluation context. This lays the foundation for treating OGNL as an execution sandbox with typed, Struts-specific context state.
|
||||
|
||||
## Motivation
|
||||
|
||||
- **Forward-looking maintenance**: stay current with OGNL development, avoid a larger migration later
|
||||
- **Real-world validation**: Struts is the primary consumer of OGNL — upgrading validates the 3.5.x generic API
|
||||
- **Java 17 baseline**: OGNL 3.5.x requires Java 17, aligning with Struts 7.x
|
||||
- **Type safety**: self-bounded generics (`OgnlContext<C>`) enable Struts to have a properly typed context instead of stringly-typed map entries
|
||||
- **Sandbox foundation**: `StrutsContext` is the first step toward isolated OGNL evaluation contexts (`OgnlRuntime` instance-based isolation is future work)
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Instance-based `OgnlRuntime` / true sandbox isolation (future OGNL work)
|
||||
- Consuming new OGNL features (null-safe operator `?.`, dual-mode evaluation) — those come as separate follow-ups
|
||||
- Behavioral changes to security model, accessor logic, or expression evaluation
|
||||
|
||||
## Current State
|
||||
|
||||
### OGNL Usage in Struts
|
||||
|
||||
- **Version**: 3.4.10 (defined in root `pom.xml` as `ognl.version`)
|
||||
- **Core dependency**: `core/pom.xml` depends on `ognl:ognl`
|
||||
- **Context creation**: 3 call sites use `Ognl.createDefaultContext()`:
|
||||
- `OgnlUtil.createDefaultContext()` (line 738)
|
||||
- `OgnlValueStack.setRoot()` (line 124)
|
||||
- `OgnlReflectionContextFactory.createDefaultContext()` (line 33)
|
||||
- **No custom OgnlContext subclass**: Struts uses `OgnlContext` directly
|
||||
- **Context state via map entries**: flags like `DENY_METHOD_EXECUTION`, `CREATE_NULL_OBJECTS`, `VALUE_STACK`, conversion state — all stored as stringly-typed map entries in `OgnlContext` and accessed via `ReflectionContextState` static methods
|
||||
|
||||
### OGNL Interface Implementations in Struts
|
||||
|
||||
| Interface | Struts Implementation |
|
||||
|---|---|
|
||||
| `MemberAccess` | `SecurityMemberAccess` |
|
||||
| `TypeConverter` | `OgnlTypeConverterWrapper` |
|
||||
| `ClassResolver` | `RootAccessor` (interface), `CompoundRootAccessor` (impl) |
|
||||
| `PropertyAccessor` | `RootAccessor`, `CompoundRootAccessor`, `ObjectProxyPropertyAccessor`, + 8 classes extending `ObjectPropertyAccessor`/`ListPropertyAccessor`/`MapPropertyAccessor`/etc. |
|
||||
| `MethodAccessor` | `RootAccessor`, `CompoundRootAccessor`, `XWorkMethodAccessor` |
|
||||
| `NullHandler` | `OgnlNullHandlerWrapper` |
|
||||
|
||||
### Tiles Plugin OGNL Usage
|
||||
|
||||
6-8 files in `plugins/tiles` use OGNL directly:
|
||||
- `ScopePropertyAccessor`, `AnyScopePropertyAccessor`, `NestedObjectDelegatePropertyAccessor`, `DelegatePropertyAccessor`
|
||||
- `OGNLAttributeEvaluator`, `PropertyAccessorDelegateFactory`, `TilesContextPropertyAccessorDelegateFactory`
|
||||
- Associated test files
|
||||
|
||||
## OGNL 3.5.x Key API Changes
|
||||
|
||||
### Self-Bounded Generics
|
||||
|
||||
All core interfaces and classes are now generic with `<C extends OgnlContext<C>>`:
|
||||
|
||||
```java
|
||||
public class OgnlContext<C extends OgnlContext<C>> implements Map<String, Object>
|
||||
public interface MemberAccess<C extends OgnlContext<C>>
|
||||
public interface ClassResolver<C extends OgnlContext<C>>
|
||||
public interface TypeConverter<C extends OgnlContext<C>>
|
||||
public interface PropertyAccessor<C extends OgnlContext<C>>
|
||||
public interface MethodAccessor<C extends OgnlContext<C>>
|
||||
public interface NullHandler<C extends OgnlContext<C>>
|
||||
public class ObjectPropertyAccessor<C extends OgnlContext<C>> implements PropertyAccessor<C>
|
||||
// ... all base accessor classes similarly parameterized
|
||||
```
|
||||
|
||||
### OgnlContext Constructor Changes
|
||||
|
||||
```java
|
||||
// New (memberAccess first, required non-null)
|
||||
public OgnlContext(MemberAccess<C> memberAccess, ClassResolver<C> classResolver, TypeConverter<C> typeConverter)
|
||||
|
||||
// Deprecated (old parameter order)
|
||||
@Deprecated(forRemoval = true)
|
||||
OgnlContext(ClassResolver<C> classResolver, TypeConverter<C> typeConverter, MemberAccess<C> memberAccess)
|
||||
```
|
||||
|
||||
### OgnlContext.Builder
|
||||
|
||||
```java
|
||||
public static class Builder<C extends OgnlContext<C>> {
|
||||
public Builder(Function<Builder<C>, C> provider)
|
||||
public Builder<C> withMemberAccess(MemberAccess<C> memberAccess)
|
||||
public Builder<C> withClassResolver(ClassResolver<C> classResolver)
|
||||
public Builder<C> withTypeConverter(TypeConverter<C> converter)
|
||||
public Builder<C> withRoot(Object value)
|
||||
public C build()
|
||||
}
|
||||
```
|
||||
|
||||
### Other Changes
|
||||
|
||||
- `SecurityManager` support removed
|
||||
- Null-safe navigation operator (`?.`) added
|
||||
- `setRoot()` deprecated in favor of `withRoot()` (fluent)
|
||||
- Java 17 baseline
|
||||
|
||||
### Unchanged
|
||||
|
||||
- `Ognl` class remains abstract with only static methods (no instance-based evaluation)
|
||||
- `OgnlRuntime` remains a static utility (global accessor/cache registration)
|
||||
|
||||
## Design
|
||||
|
||||
### Approach: Direct StrutsContext Construction
|
||||
|
||||
Struts creates `StrutsContext` directly, bypassing `Ognl.createDefaultContext()`. This gives Struts full ownership of context lifecycle and avoids the global-state issues of `Ognl.withBuilderProvider()`.
|
||||
|
||||
### StrutsContext
|
||||
|
||||
```java
|
||||
package org.apache.struts2.ognl;
|
||||
|
||||
public class StrutsContext extends OgnlContext<StrutsContext> {
|
||||
|
||||
// Phase 1: just the constructor, delegate to super
|
||||
public StrutsContext(SecurityMemberAccess memberAccess,
|
||||
RootAccessor resolver,
|
||||
OgnlTypeConverterWrapper converter) {
|
||||
super(memberAccess, resolver, converter);
|
||||
}
|
||||
|
||||
// Phase 2 (incremental): promote map entries to typed fields
|
||||
// private ValueStack valueStack;
|
||||
// private boolean reportErrorsOnNoProperty;
|
||||
// private boolean throwExceptionOnFailure;
|
||||
// private boolean createNullObjects;
|
||||
// private boolean denyMethodExecution;
|
||||
// private boolean denyIndexedAccessExecution;
|
||||
// private String conversionPropertyFullName;
|
||||
// private String currentPropertyPath;
|
||||
// private Class<?> lastBeanClassAccessed;
|
||||
// private String lastBeanPropertyAccessed;
|
||||
}
|
||||
```
|
||||
|
||||
Phase 1 introduces the class with zero behavioral change — it's just an `OgnlContext` subclass. The typed fields are a follow-up.
|
||||
|
||||
### Generic Type Ripple
|
||||
|
||||
All OGNL interface implementations parameterize with `<StrutsContext>`:
|
||||
|
||||
```java
|
||||
// Core interfaces
|
||||
public class SecurityMemberAccess implements MemberAccess<StrutsContext>
|
||||
public class OgnlTypeConverterWrapper implements ognl.TypeConverter<StrutsContext>
|
||||
public interface RootAccessor extends PropertyAccessor<StrutsContext>, MethodAccessor<StrutsContext>, ClassResolver<StrutsContext>
|
||||
public class OgnlNullHandlerWrapper implements ognl.NullHandler<StrutsContext>
|
||||
|
||||
// Accessors (extend generic base classes)
|
||||
public class CompoundRootAccessor implements RootAccessor
|
||||
public class ObjectProxyPropertyAccessor implements PropertyAccessor<StrutsContext>
|
||||
public class ObjectAccessor extends ObjectPropertyAccessor<StrutsContext>
|
||||
public class ParameterPropertyAccessor extends ObjectPropertyAccessor<StrutsContext>
|
||||
public class HttpParametersPropertyAccessor extends ObjectPropertyAccessor<StrutsContext>
|
||||
public class XWorkObjectPropertyAccessor extends ObjectPropertyAccessor<StrutsContext>
|
||||
public class XWorkEnumerationAccessor extends EnumerationPropertyAccessor<StrutsContext> // verify base class
|
||||
public class XWorkIteratorPropertyAccessor extends IteratorPropertyAccessor<StrutsContext> // verify base class
|
||||
public class XWorkCollectionPropertyAccessor extends ObjectPropertyAccessor<StrutsContext>
|
||||
public class XWorkListPropertyAccessor extends ListPropertyAccessor<StrutsContext>
|
||||
public class XWorkMapPropertyAccessor extends MapPropertyAccessor<StrutsContext>
|
||||
public class XWorkMethodAccessor extends ObjectMethodAccessor<StrutsContext>
|
||||
```
|
||||
|
||||
Method signatures change `OgnlContext` parameters to `StrutsContext` throughout.
|
||||
|
||||
### Context Creation
|
||||
|
||||
Replace `Ognl.createDefaultContext()` with direct construction:
|
||||
|
||||
```java
|
||||
// OgnlUtil.createDefaultContext()
|
||||
protected StrutsContext createDefaultContext(Object root, ClassResolver<StrutsContext> resolver) {
|
||||
if (resolver == null) {
|
||||
resolver = container.getInstance(RootAccessor.class);
|
||||
}
|
||||
StrutsContext ctx = new StrutsContext(
|
||||
container.getInstance(SecurityMemberAccess.class), resolver, defaultConverter);
|
||||
ctx.withRoot(root);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// OgnlValueStack.setRoot()
|
||||
StrutsContext ognlContext = new StrutsContext(securityMemberAccess, accessor,
|
||||
new OgnlTypeConverterWrapper(xworkConverter));
|
||||
ognlContext.withRoot(this.root);
|
||||
|
||||
// OgnlReflectionContextFactory — already @Deprecated(forRemoval=true) since 6.8.0
|
||||
// Keep using Ognl.createDefaultContext(root) with raw type, or remove entirely
|
||||
```
|
||||
|
||||
### Tiles Plugin
|
||||
|
||||
The tiles plugin accessors operate on tiles-specific objects, not on `StrutsContext` directly. Options:
|
||||
- Parameterize with raw `OgnlContext` (use `PropertyAccessor<OgnlContext>`) if OGNL allows it
|
||||
- Use wildcard `PropertyAccessor<?>` if supported
|
||||
- Parameterize with `StrutsContext` if tiles always runs within a Struts context
|
||||
|
||||
Decision: determine during implementation based on what compiles cleanly.
|
||||
|
||||
### XWorkTypeConverterWrapper
|
||||
|
||||
Currently casts `Map` context to `OgnlContext`. After upgrade, `ognl.TypeConverter<StrutsContext>` passes `StrutsContext` directly — the cast goes away. Struts' own `TypeConverter` interface (in `conversion` package) may also need its `convertValue` signature updated.
|
||||
|
||||
### ReflectionContextState
|
||||
|
||||
Initially unchanged — continues to work via `Map<String, Object>` interface that `StrutsContext` inherits from `OgnlContext`. Promoting to typed fields is a follow-up.
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Version bump + StrutsContext + generics (this effort)
|
||||
|
||||
1. Bump `ognl.version` to `3.5.0-BETA4` in root `pom.xml`
|
||||
2. Create `StrutsContext extends OgnlContext<StrutsContext>` (constructor only)
|
||||
3. Update all OGNL interface implementations with `<StrutsContext>` type parameter (~20 classes in core)
|
||||
4. Update method signatures: `OgnlContext` → `StrutsContext` in all accessor/handler implementations
|
||||
5. Replace `Ognl.createDefaultContext()` with direct `StrutsContext` construction (3 call sites)
|
||||
6. Update tiles plugin accessor classes (~6-8 files)
|
||||
7. Update test files (~50+ files referencing `OgnlContext`)
|
||||
8. Verify all tests pass
|
||||
|
||||
### Phase 2: Typed context fields (follow-up)
|
||||
|
||||
- Promote `ReflectionContextState` map entries to `StrutsContext` typed fields
|
||||
- Update accessors to use typed getters instead of `context.get("string.key")`
|
||||
- Deprecate `ReflectionContextState` static methods
|
||||
|
||||
### Phase 3: Sandbox features (future, requires OGNL changes)
|
||||
|
||||
- Instance-based `OgnlRuntime` (OGNL-side work)
|
||||
- Per-sandbox accessor registrations
|
||||
- Isolated evaluation engines
|
||||
|
||||
## Risk Areas
|
||||
|
||||
### OgnlRuntime global statics
|
||||
|
||||
`OgnlRuntime.setPropertyAccessor(Class<?>, PropertyAccessor<C>)` is generic but the registration is global. Registering `PropertyAccessor<StrutsContext>` may cause unchecked warnings or issues when OGNL internally retrieves accessors with a different context type. May need raw types at registration boundary.
|
||||
|
||||
### OGNL internal context preservation
|
||||
|
||||
If OGNL internally creates new `OgnlContext` instances during expression evaluation (rather than preserving the passed-in `StrutsContext`), typed fields would be lost. BETA1 addressed "context root preservation during nested evaluations" but this needs runtime verification.
|
||||
|
||||
### Tiles plugin type compatibility
|
||||
|
||||
Tiles accessors may not naturally fit `StrutsContext` parameterization. Need to determine the right generic type during implementation.
|
||||
|
||||
### OGNL BETA stability
|
||||
|
||||
OGNL 3.5.0 is still in BETA. API changes may occur in subsequent releases. This is acceptable given the user is an OGNL contributor and can influence the API.
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
- Struts compiles and all tests pass against OGNL 3.5.0-BETA4
|
||||
- `StrutsContext` exists as the framework's OGNL context class
|
||||
- All OGNL interface implementations are properly parameterized with `<StrutsContext>`
|
||||
- Foundation is in place for typed context fields and eventual sandbox isolation
|
||||
- Any OGNL API issues discovered are reported/fixed upstream
|
||||
Reference in New Issue
Block a user