From e2e8fa1f332c11ec4de357561c025cb771d8974e Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Mon, 6 Apr 2026 18:14:29 +0200 Subject: [PATCH] WW-5326 feat(ognl): introduce StrutsContext extending OgnlContext Co-Authored-By: Claude Opus 4.6 --- .../apache/struts2/ognl/StrutsContext.java | 53 ++++++++++++++++++ .../struts2/ognl/StrutsContextTest.java | 54 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 core/src/main/java/org/apache/struts2/ognl/StrutsContext.java create mode 100644 core/src/test/java/org/apache/struts2/ognl/StrutsContextTest.java diff --git a/core/src/main/java/org/apache/struts2/ognl/StrutsContext.java b/core/src/main/java/org/apache/struts2/ognl/StrutsContext.java new file mode 100644 index 000000000..e2b7b4b93 --- /dev/null +++ b/core/src/main/java/org/apache/struts2/ognl/StrutsContext.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.struts2.ognl; + +import ognl.ClassResolver; +import ognl.MemberAccess; +import ognl.OgnlContext; +import ognl.TypeConverter; + +/** + * Struts-specific OGNL evaluation context. Extends {@link OgnlContext} with the + * self-bounded generic parameter to enable type-safe access in all OGNL interface + * implementations ({@link MemberAccess}, {@link ognl.PropertyAccessor}, etc.). + * + *

Phase 1: minimal subclass delegating to super constructors. + * Future phases will promote stringly-typed map entries (e.g. {@code DENY_METHOD_EXECUTION}, + * {@code CREATE_NULL_OBJECTS}) to proper typed fields.

+ * + * @since 7.2.0 + */ +public class StrutsContext extends OgnlContext { + + public StrutsContext(MemberAccess memberAccess) { + super(memberAccess); + } + + public StrutsContext(MemberAccess memberAccess, + ClassResolver classResolver) { + super(memberAccess, classResolver); + } + + public StrutsContext(MemberAccess memberAccess, + ClassResolver classResolver, + TypeConverter typeConverter) { + super(memberAccess, classResolver, typeConverter); + } +} diff --git a/core/src/test/java/org/apache/struts2/ognl/StrutsContextTest.java b/core/src/test/java/org/apache/struts2/ognl/StrutsContextTest.java new file mode 100644 index 000000000..be0a07305 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/ognl/StrutsContextTest.java @@ -0,0 +1,54 @@ +package org.apache.struts2.ognl; + +import ognl.ClassResolver; +import ognl.MemberAccess; +import ognl.TypeConverter; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@SuppressWarnings("unchecked") +class StrutsContextTest { + + @Test + void shouldCreateContextWithRequiredMemberAccess() { + MemberAccess memberAccess = mock(MemberAccess.class); + var context = new StrutsContext(memberAccess); + + assertThat(context).isNotNull(); + assertThat(context.getMemberAccess()).isSameAs(memberAccess); + } + + @Test + void shouldCreateContextWithAllComponents() { + MemberAccess memberAccess = mock(MemberAccess.class); + ClassResolver classResolver = mock(ClassResolver.class); + TypeConverter typeConverter = mock(TypeConverter.class); + + var context = new StrutsContext(memberAccess, classResolver, typeConverter); + + assertThat(context.getMemberAccess()).isSameAs(memberAccess); + assertThat(context.getClassResolver()).isSameAs(classResolver); + assertThat(context.getTypeConverter()).isSameAs(typeConverter); + } + + @Test + void shouldSupportRootObject() { + MemberAccess memberAccess = mock(MemberAccess.class); + var root = new Object(); + var context = new StrutsContext(memberAccess); + context.withRoot(root); + + assertThat(context.getRoot()).isSameAs(root); + } + + @Test + void shouldImplementMapInterface() { + MemberAccess memberAccess = mock(MemberAccess.class); + var context = new StrutsContext(memberAccess); + + context.put("testKey", "testValue"); + assertThat(context.get("testKey")).isEqualTo("testValue"); + } +}