From 144c30f62ae852379e11176c438c4d48df5119c3 Mon Sep 17 00:00:00 2001 From: Lukasz Lenart Date: Sat, 24 Oct 2026 10:23:53 +0200 Subject: [PATCH] WW-5792 fix(config): warn instead of silently skipping non-public action classes verifyAction threw "is not public" inside the try whose RuntimeException handler swallowed it, so a non-public action class only produced a misleading INFO line. It now logs a WARN naming the class and location and skips the constructor check, so startup behaves as before. Failing startup is tracked in WW-5809 for 8.0.0. Co-Authored-By: Claude Opus 5.5 --- .../XmlDocConfigurationProvider.java | 3 +- .../config/providers/NonPublicAction.java | 29 +++++++++++++++ .../XmlConfigurationProviderActionsTest.java | 36 +++++++++++++++++++ .../xwork-test-action-non-public.xml | 32 +++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/org/apache/struts2/config/providers/NonPublicAction.java create mode 100644 core/src/test/resources/org/apache/struts2/config/providers/xwork-test-action-non-public.xml diff --git a/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java b/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java index e7e7d40663..0080d97f39 100644 --- a/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java +++ b/core/src/main/java/org/apache/struts2/config/providers/XmlDocConfigurationProvider.java @@ -542,7 +542,8 @@ protected void verifyAction(String className, Location loc) { Class clazz = allowAndLoadClass(className); if (objectFactory.isNoArgConstructorRequired()) { if (!Modifier.isPublic(clazz.getModifiers())) { - throw new ConfigurationException("Action class [" + className + "] is not public", loc); + LOG.warn("Action class [{}] is not public, this will fail at startup in Struts 8.0.0 - {}", className, loc); + return; } clazz.getConstructor(); } diff --git a/core/src/test/java/org/apache/struts2/config/providers/NonPublicAction.java b/core/src/test/java/org/apache/struts2/config/providers/NonPublicAction.java new file mode 100644 index 0000000000..6a54551db7 --- /dev/null +++ b/core/src/test/java/org/apache/struts2/config/providers/NonPublicAction.java @@ -0,0 +1,29 @@ +/* + * 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.config.providers; + +import org.apache.struts2.action.Action; + +class NonPublicAction implements Action { + + public String execute() { + return SUCCESS; + } + +} diff --git a/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderActionsTest.java b/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderActionsTest.java index 076fdaa6df..a7bda3a5ef 100644 --- a/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderActionsTest.java +++ b/core/src/test/java/org/apache/struts2/config/providers/XmlConfigurationProviderActionsTest.java @@ -18,6 +18,11 @@ */ package org.apache.struts2.config.providers; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.struts2.ObjectFactory; import org.apache.struts2.SimpleAction; import org.apache.struts2.config.ConfigurationException; @@ -161,6 +166,24 @@ public void testInvalidActions() { } } + public void testNonPublicActionWarnsAndLoads() { + final String filename = "org/apache/struts2/config/providers/xwork-test-action-non-public.xml"; + LogCapture logCapture = new LogCapture(); + Logger logger = (Logger) LogManager.getLogger(XmlConfigurationProvider.class); + logger.addAppender(logCapture); + logCapture.start(); + try { + buildConfigurationProvider(filename); + } finally { + logCapture.stop(); + logger.removeAppender(logCapture); + } + + assertNotNull(configuration.getPackageConfig("default").getActionConfigs().get("NonPublic")); + assertTrue(logCapture.events.stream().anyMatch(event -> event.getLevel() == Level.WARN + && event.getMessage().getFormattedMessage().contains("Action class [" + NonPublicAction.class.getName() + "] is not public"))); + } + public void testPackageDefaultClassRef() { final String filename = "org/apache/struts2/config/providers/xwork-test-actions-packagedefaultclassref.xml"; @@ -221,4 +244,17 @@ protected void setUp() throws Exception { exceptionMappings = new ArrayList<>(); this.objectFactory = container.getInstance(ObjectFactory.class); } + + private static class LogCapture extends AbstractAppender { + private final List events = new ArrayList<>(); + + LogCapture() { + super("LogCapture", null, null, false, null); + } + + @Override + public void append(LogEvent event) { + events.add(event.toImmutable()); + } + } } diff --git a/core/src/test/resources/org/apache/struts2/config/providers/xwork-test-action-non-public.xml b/core/src/test/resources/org/apache/struts2/config/providers/xwork-test-action-non-public.xml new file mode 100644 index 0000000000..4a55fc209a --- /dev/null +++ b/core/src/test/resources/org/apache/struts2/config/providers/xwork-test-action-non-public.xml @@ -0,0 +1,32 @@ + + + + + + + + + + +