This is an automated email from the ASF dual-hosted git repository.
lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts.git
The following commit(s) were added to refs/heads/main by this push:
new a8626db3a WW-5687 feat(core): clear the conversion and validator
caches on Dispatcher.cleanup() (#1930)
a8626db3a is described below
commit a8626db3a6267d2d1dfc9f7fed3dfa7b5634be9d
Author: Lukasz Lenart <[email protected]>
AuthorDate: Sun Sep 13 17:27:56 2026 +0200
WW-5687 feat(core): clear the conversion and validator caches on
Dispatcher.cleanup() (#1930)
Follow-on of WW-5537. StrutsTypeConverterHolder and
DefaultActionValidatorManager
hold per-instance caches of application Class objects and
converter/validator
instances; they are now cleared with the other InternalDestroyable beans.
The holders are not registered as InternalDestroyable themselves: the
container
keys singletons by (type, name), so a second registration would build a
second,
empty instance and clear nothing. Two adapter beans @Inject the in-use
singleton
(resolved through the StrutsBeanSelectionProvider alias, so a custom
struts.actionValidatorManager or struts.converter.holder binding is
covered) and
call the new clearCache() default method, which is a no-op for third-party
implementations.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../conversion/StrutsTypeConverterHolder.java | 8 ++++
.../struts2/conversion/TypeConverterHolder.java | 9 +++++
.../ActionValidatorManagerDestroyable.java | 44 ++++++++++++++++++++
.../dispatcher/TypeConverterHolderDestroyable.java | 44 ++++++++++++++++++++
.../struts2/validator/ActionValidatorManager.java | 9 +++++
.../validator/DefaultActionValidatorManager.java | 6 +++
core/src/main/resources/struts-beans.xml | 4 ++
.../struts2/dispatcher/DispatcherCleanupTest.java | 47 +++++++++++++++++++++-
8 files changed, 170 insertions(+), 1 deletion(-)
diff --git
a/core/src/main/java/org/apache/struts2/conversion/StrutsTypeConverterHolder.java
b/core/src/main/java/org/apache/struts2/conversion/StrutsTypeConverterHolder.java
index 4b3d5ca38..c685c037e 100644
---
a/core/src/main/java/org/apache/struts2/conversion/StrutsTypeConverterHolder.java
+++
b/core/src/main/java/org/apache/struts2/conversion/StrutsTypeConverterHolder.java
@@ -197,4 +197,12 @@ public class StrutsTypeConverterHolder implements
TypeConverterHolder {
unknownMappingsInternal.add(className);
}
+ @Override
+ public void clearCache() {
+ defaultMappings.clear();
+ mappings.clear();
+ unknownMappingsInternal.clear();
+ unknownMappings.clear();
+ }
+
}
diff --git
a/core/src/main/java/org/apache/struts2/conversion/TypeConverterHolder.java
b/core/src/main/java/org/apache/struts2/conversion/TypeConverterHolder.java
index 8fc1c507a..7c818085a 100644
--- a/core/src/main/java/org/apache/struts2/conversion/TypeConverterHolder.java
+++ b/core/src/main/java/org/apache/struts2/conversion/TypeConverterHolder.java
@@ -152,4 +152,13 @@ public interface TypeConverterHolder {
return mapping;
}
+ /**
+ * Drops every registered and cached mapping. Called during {@code
Dispatcher.cleanup()};
+ * the default is a no-op so implementations that hold no state need not
override it.
+ *
+ * @since 7.4.0
+ */
+ default void clearCache() {
+ }
+
}
diff --git
a/core/src/main/java/org/apache/struts2/dispatcher/ActionValidatorManagerDestroyable.java
b/core/src/main/java/org/apache/struts2/dispatcher/ActionValidatorManagerDestroyable.java
new file mode 100644
index 000000000..387bfd8e4
--- /dev/null
+++
b/core/src/main/java/org/apache/struts2/dispatcher/ActionValidatorManagerDestroyable.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dispatcher;
+
+import org.apache.struts2.inject.Inject;
+import org.apache.struts2.validator.ActionValidatorManager;
+
+/**
+ * Clears the {@link ActionValidatorManager} in use. Registered as a separate
bean rather than
+ * making the manager itself an {@link InternalDestroyable}: the container
keys singletons by
+ * (type, name), so a second registration of the manager class would build a
second, empty instance.
+ *
+ * @since 7.4.0
+ */
+public class ActionValidatorManagerDestroyable implements InternalDestroyable {
+
+ private ActionValidatorManager actionValidatorManager;
+
+ @Inject
+ public void setActionValidatorManager(ActionValidatorManager
actionValidatorManager) {
+ this.actionValidatorManager = actionValidatorManager;
+ }
+
+ @Override
+ public void destroy() {
+ actionValidatorManager.clearCache();
+ }
+}
diff --git
a/core/src/main/java/org/apache/struts2/dispatcher/TypeConverterHolderDestroyable.java
b/core/src/main/java/org/apache/struts2/dispatcher/TypeConverterHolderDestroyable.java
new file mode 100644
index 000000000..77a4b67cd
--- /dev/null
+++
b/core/src/main/java/org/apache/struts2/dispatcher/TypeConverterHolderDestroyable.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dispatcher;
+
+import org.apache.struts2.conversion.TypeConverterHolder;
+import org.apache.struts2.inject.Inject;
+
+/**
+ * Clears the {@link TypeConverterHolder} in use. Registered as a separate
bean rather than making
+ * the holder itself an {@link InternalDestroyable}: the container keys
singletons by (type, name),
+ * so a second registration of the holder class would build a second, empty
instance.
+ *
+ * @since 7.4.0
+ */
+public class TypeConverterHolderDestroyable implements InternalDestroyable {
+
+ private TypeConverterHolder typeConverterHolder;
+
+ @Inject
+ public void setTypeConverterHolder(TypeConverterHolder
typeConverterHolder) {
+ this.typeConverterHolder = typeConverterHolder;
+ }
+
+ @Override
+ public void destroy() {
+ typeConverterHolder.clearCache();
+ }
+}
diff --git
a/core/src/main/java/org/apache/struts2/validator/ActionValidatorManager.java
b/core/src/main/java/org/apache/struts2/validator/ActionValidatorManager.java
index 99356a6b3..4771f585b 100644
---
a/core/src/main/java/org/apache/struts2/validator/ActionValidatorManager.java
+++
b/core/src/main/java/org/apache/struts2/validator/ActionValidatorManager.java
@@ -87,4 +87,13 @@ public interface ActionValidatorManager {
* @throws ValidationException if an error happens when validating the
action.
*/
void validate(Object object, String context, ValidatorContext
validatorContext, String method) throws ValidationException;
+
+ /**
+ * Drops every cached validator configuration. Called during {@code
Dispatcher.cleanup()};
+ * the default is a no-op so implementations that hold no state need not
override it.
+ *
+ * @since 7.4.0
+ */
+ default void clearCache() {
+ }
}
diff --git
a/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java
b/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java
index b2ff42161..3bf118bd9 100644
---
a/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java
+++
b/core/src/main/java/org/apache/struts2/validator/DefaultActionValidatorManager.java
@@ -176,6 +176,12 @@ public class DefaultActionValidatorManager implements
ActionValidatorManager {
return getValidators(clazz, context, null);
}
+ @Override
+ public void clearCache() {
+ validatorCache.clear();
+ validatorFileCache.clear();
+ }
+
@Override
public void validate(Object object, String context, ValidatorContext
validatorContext, String method) throws ValidationException {
List<Validator> validators = getValidators(object.getClass(), context,
method);
diff --git a/core/src/main/resources/struts-beans.xml
b/core/src/main/resources/struts-beans.xml
index e53710c22..d12535b9c 100644
--- a/core/src/main/resources/struts-beans.xml
+++ b/core/src/main/resources/struts-beans.xml
@@ -290,5 +290,9 @@
class="org.apache.struts2.dispatcher.FreemarkerCacheDestroyable"/>
<bean type="org.apache.struts2.dispatcher.InternalDestroyable"
name="debugUtilsCache"
class="org.apache.struts2.dispatcher.DebugUtilsCacheDestroyable"/>
+ <bean type="org.apache.struts2.dispatcher.InternalDestroyable"
name="typeConverterHolder"
+
class="org.apache.struts2.dispatcher.TypeConverterHolderDestroyable"/>
+ <bean type="org.apache.struts2.dispatcher.InternalDestroyable"
name="actionValidatorManager"
+
class="org.apache.struts2.dispatcher.ActionValidatorManagerDestroyable"/>
</struts>
diff --git
a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherCleanupTest.java
b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherCleanupTest.java
index d55cb4438..c5ccbc74b 100644
---
a/core/src/test/java/org/apache/struts2/dispatcher/DispatcherCleanupTest.java
+++
b/core/src/test/java/org/apache/struts2/dispatcher/DispatcherCleanupTest.java
@@ -21,10 +21,14 @@ package org.apache.struts2.dispatcher;
import org.apache.struts2.ActionContext;
import org.apache.struts2.StrutsJUnit4InternalTestCase;
import org.apache.struts2.components.Component;
+import org.apache.struts2.conversion.TypeConverterHolder;
import org.apache.struts2.inject.Container;
import org.apache.struts2.ognl.accessor.CompoundRootAccessor;
import org.apache.struts2.util.DebugUtils;
import org.apache.struts2.util.fs.DefaultFileManager;
+import org.apache.struts2.validator.ActionValidatorManager;
+import org.apache.struts2.validator.DefaultActionValidatorManager;
+import org.apache.struts2.validator.ValidatorConfig;
import org.junit.Test;
import java.lang.reflect.Field;
@@ -58,7 +62,7 @@ public class DispatcherCleanupTest extends
StrutsJUnit4InternalTestCase {
Set<String> expected = new HashSet<>(Arrays.asList(
"componentCache", "compoundRootAccessor", "defaultFileManager",
"scopeInterceptorCache", "ognlCache",
"finalizableReferenceQueue",
- "freemarkerCache", "debugUtilsCache"
+ "freemarkerCache", "debugUtilsCache", "typeConverterHolder",
"actionValidatorManager"
));
assertThat(names).containsAll(expected);
}
@@ -132,6 +136,47 @@ public class DispatcherCleanupTest extends
StrutsJUnit4InternalTestCase {
assertThat(lazyCache).isEmpty();
}
+ @Test
+ public void cleanupClearsTypeConverterHolderCaches() {
+ initDispatcher(emptyMap());
+
+ Container container =
dispatcher.getConfigurationManager().getConfiguration().getContainer();
+ TypeConverterHolder holder =
container.getInstance(TypeConverterHolder.class);
+ holder.addMapping(DispatcherCleanupTest.class, Map.of("field", new
Object()));
+ holder.addUnknownMapping(DispatcherCleanupTest.class.getName());
+ assertThat(holder.getMapping(DispatcherCleanupTest.class)).isNotNull();
+
assertThat(holder.containsUnknownMapping(DispatcherCleanupTest.class.getName())).isTrue();
+
+ dispatcher.cleanup();
+
+ assertThat(holder.getMapping(DispatcherCleanupTest.class)).isNull();
+
assertThat(holder.containsUnknownMapping(DispatcherCleanupTest.class.getName())).isFalse();
+ }
+
+ @Test
+ public void cleanupClearsActionValidatorManagerCaches() throws Exception {
+ initDispatcher(emptyMap());
+
+ Container container =
dispatcher.getConfigurationManager().getConfiguration().getContainer();
+ ActionValidatorManager manager =
container.getInstance(ActionValidatorManager.class);
+ Map<String, List<ValidatorConfig>> validatorCache =
instanceMap(manager, "validatorCache");
+ Map<String, List<ValidatorConfig>> validatorFileCache =
instanceMap(manager, "validatorFileCache");
+ validatorCache.put("test-key", new ArrayList<>());
+ validatorFileCache.put("test-file", new ArrayList<>());
+
+ dispatcher.cleanup();
+
+ assertThat(validatorCache).isEmpty();
+ assertThat(validatorFileCache).isEmpty();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map<String, List<ValidatorConfig>>
instanceMap(ActionValidatorManager manager, String fieldName) throws Exception {
+ Field field =
DefaultActionValidatorManager.class.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return (Map<String, List<ValidatorConfig>>) field.get(manager);
+ }
+
@Test
public void cleanupClearsDispatcherListeners() throws Exception {
initDispatcher(emptyMap());