This is an automated email from the ASF dual-hosted git repository.

vy pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new 3c60ae0c04 Fix exceptions in the JMX integration (#4185)
3c60ae0c04 is described below

commit 3c60ae0c04d2f310e33c77d6e22a50023d80193a
Author: Sebastien Tardif <[email protected]>
AuthorDate: Fri Aug 14 01:18:49 2026 -0700

    Fix exceptions in the JMX integration (#4185)
    
    Signed-off-by: Sebastien Tardif <[email protected]>
    Co-authored-by: Ramanathan <[email protected]>
    Co-authored-by: Volkan Yazıcı <[email protected]>
---
 .../org/apache/log4j/jmx/AppenderDynamicMBean.java |   8 ++
 .../org/apache/log4j/jmx/LoggerDynamicMBean.java   |   8 +-
 .../apache/log4j/jmx/AppenderDynamicMBeanTest.java |  83 +++++++++++++++++
 .../apache/log4j/jmx/LoggerDynamicMBeanTest.java   | 102 +++++++++++++++++++++
 src/changelog/.2.x.x/4185_fix_jmx_mbean_npe.xml    |  12 +++
 5 files changed, 212 insertions(+), 1 deletion(-)

diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java
index 2ee683240a..053388fb18 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java
@@ -187,6 +187,14 @@ public class AppenderDynamicMBean extends 
AbstractDynamicMBean {
         } else if (operationName.equals("setLayout")) {
             final Layout layout =
                     (Layout) OptionConverter.instantiateByClassName((String) 
params[0], Layout.class, null);
+            if (layout == null) {
+                final String message = "Could not instantiate layout class [" 
+ params[0] + "] for appender ["
+                        + getAppenderName(appender) + "].";
+                cat.error(message);
+                // Fail via MBeanException so JMX clients can distinguish 
failure from
+                // success (setLayout is declared void; a return string is not 
reliable).
+                throw new MBeanException(new 
IllegalArgumentException(message), message);
+            }
             appender.setLayout(layout);
             registerLayoutMBean(layout);
         }
diff --git 
a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java 
b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java
index 7be0f070f5..5d6825dd2d 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java
@@ -63,10 +63,16 @@ public class LoggerDynamicMBean extends 
AbstractDynamicMBean implements Notifica
         buildDynamicMBeanInfo();
     }
 
-    void addAppender(final String appenderClass, final String appenderName) {
+    void addAppender(final String appenderClass, final String appenderName) 
throws MBeanException {
         cat.debug("addAppender called with " + appenderClass + ", " + 
appenderName);
         final Appender appender =
                 (Appender) 
OptionConverter.instantiateByClassName(appenderClass, 
org.apache.log4j.Appender.class, null);
+        if (appender == null) {
+            final String message =
+                    "Could not instantiate appender class [" + appenderClass + 
"] for name [" + appenderName + "].";
+            cat.error(message);
+            throw new MBeanException(new IllegalArgumentException(message), 
message);
+        }
         appender.setName(appenderName);
         logger.addAppender(appender);
 
diff --git 
a/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/AppenderDynamicMBeanTest.java
 
b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/AppenderDynamicMBeanTest.java
new file mode 100644
index 0000000000..597bd3822a
--- /dev/null
+++ 
b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/AppenderDynamicMBeanTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.log4j.jmx;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.PatternLayout;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Regression for JMX {@code setLayout}: instantiateByClassName may return 
null.
+ */
+class AppenderDynamicMBeanTest {
+
+    private static final String[] SET_LAYOUT_SIGNATURE = 
{String.class.getName()};
+
+    private MBeanServer server;
+
+    @BeforeEach
+    void createMBeanServer() {
+        server = MBeanServerFactory.newMBeanServer();
+    }
+
+    /**
+     * Registration is required: {@code preRegister} injects the server that
+     * {@code registerLayoutMBean} dereferences on the success path.
+     */
+    private AppenderDynamicMBean registerAppenderMBean(final ConsoleAppender 
appender) throws Exception {
+        final AppenderDynamicMBean mbean = new AppenderDynamicMBean(appender);
+        server.registerMBean(mbean, new ObjectName("log4j:appender=" + 
appender.getName()));
+        return mbean;
+    }
+
+    @Test
+    void setLayoutFailsWhenClassCannotBeInstantiated() throws Exception {
+        final ConsoleAppender appender = new ConsoleAppender();
+        appender.setName("jmx-layout-test");
+        final AppenderDynamicMBean mbean = registerAppenderMBean(appender);
+
+        final MBeanException thrown = assertThrows(
+                MBeanException.class,
+                () -> mbean.invoke(
+                        "setLayout", new Object[] 
{"this.class.does.not.exist.MissingLayout"}, SET_LAYOUT_SIGNATURE));
+        assertTrue(thrown.getMessage().contains("Could not instantiate layout 
class"));
+        assertInstanceOf(IllegalArgumentException.class, 
thrown.getTargetException());
+        assertNull(appender.getLayout());
+    }
+
+    @Test
+    void setLayoutStillAttachesValidLayout() throws Exception {
+        final ConsoleAppender appender = new ConsoleAppender();
+        appender.setName("jmx-layout-valid");
+        final AppenderDynamicMBean mbean = registerAppenderMBean(appender);
+
+        mbean.invoke("setLayout", new Object[] 
{PatternLayout.class.getName()}, SET_LAYOUT_SIGNATURE);
+        assertInstanceOf(PatternLayout.class, appender.getLayout());
+        assertTrue(server.isRegistered(
+                new ObjectName("log4j:appender=" + appender.getName() + 
",layout=" + PatternLayout.class.getName())));
+    }
+}
diff --git 
a/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/LoggerDynamicMBeanTest.java 
b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/LoggerDynamicMBeanTest.java
new file mode 100644
index 0000000000..8bd45969d5
--- /dev/null
+++ 
b/log4j-1.2-api/src/test/java/org/apache/log4j/jmx/LoggerDynamicMBeanTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.log4j.jmx;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Enumeration;
+import javax.management.MBeanException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+import org.apache.log4j.Appender;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Logger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Regression for invalid JMX {@code addAppender} class names: instantiation 
can
+ * return null, which must not NPE when calling {@code setName} and must not be
+ * reported to the client as a successful invocation.
+ */
+class LoggerDynamicMBeanTest {
+
+    private static final String[] ADD_APPENDER_SIGNATURE = 
{String.class.getName(), String.class.getName()};
+
+    private MBeanServer server;
+
+    @BeforeEach
+    void createMBeanServer() {
+        server = MBeanServerFactory.newMBeanServer();
+    }
+
+    /**
+     * Register through an {@link MBeanServer} so the bean goes through
+     * {@code preRegister}, matching real JMX use (and {@code 
AppenderDynamicMBeanTest}).
+     */
+    private LoggerDynamicMBean registerLoggerMBean(final Logger logger) throws 
Exception {
+        final LoggerDynamicMBean mbean = new LoggerDynamicMBean(logger);
+        final String name = logger.getName().isEmpty() ? "root" : 
logger.getName();
+        // Same ObjectName shape as HierarchyDynamicMBean.addLoggerMBean.
+        server.registerMBean(mbean, new ObjectName("log4j", "logger", name));
+        return mbean;
+    }
+
+    @Test
+    void addAppenderFailsWhenClassCannotBeInstantiated() throws Exception {
+        final Logger logger = 
Logger.getLogger("jmx.LoggerDynamicMBeanTest.invalid");
+        final LoggerDynamicMBean mbean = registerLoggerMBean(logger);
+
+        final MBeanException thrown = assertThrows(
+                MBeanException.class,
+                () -> mbean.invoke(
+                        "addAppender",
+                        new Object[] 
{"this.class.does.not.exist.MissingAppender", "should-not-attach"},
+                        ADD_APPENDER_SIGNATURE));
+        assertTrue(thrown.getMessage().contains("Could not instantiate 
appender class"));
+        assertInstanceOf(IllegalArgumentException.class, 
thrown.getTargetException());
+        assertFalse(hasAppenderNamed(logger, "should-not-attach"));
+    }
+
+    @Test
+    void addAppenderStillAttachesValidAppender() throws Exception {
+        final Logger logger = 
Logger.getLogger("jmx.LoggerDynamicMBeanTest.valid");
+        final LoggerDynamicMBean mbean = registerLoggerMBean(logger);
+
+        final Object result = mbean.invoke(
+                "addAppender", new Object[] {ConsoleAppender.class.getName(), 
"console-jmx"}, ADD_APPENDER_SIGNATURE);
+        assertTrue(hasAppenderNamed(logger, "console-jmx"));
+        // Legacy success return value, pinned so the fix cannot silently 
change it.
+        assertEquals("Hello world.", result);
+    }
+
+    private static boolean hasAppenderNamed(final Logger logger, final String 
name) {
+        final Enumeration enumeration = logger.getAllAppenders();
+        while (enumeration.hasMoreElements()) {
+            final Appender appender = (Appender) enumeration.nextElement();
+            if (name.equals(appender.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/changelog/.2.x.x/4185_fix_jmx_mbean_npe.xml 
b/src/changelog/.2.x.x/4185_fix_jmx_mbean_npe.xml
new file mode 100644
index 0000000000..65e2259a3f
--- /dev/null
+++ b/src/changelog/.2.x.x/4185_fix_jmx_mbean_npe.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns="https://logging.apache.org/xml/ns";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="
+           https://logging.apache.org/xml/ns
+           https://logging.apache.org/xml/ns/log4j-changelog-0.xsd";
+       type="fixed">
+    <issue id="4185" 
link="https://github.com/apache/logging-log4j2/pull/4185"/>
+    <description format="asciidoc">
+        Fix exceptions in JMX integration
+    </description>
+</entry>

Reply via email to