You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by gg...@apache.org on 2022/03/21 23:27:57 UTC

[logging-log4j2] branch release-2.x updated: Add missing package for binary compatibility

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

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


The following commit(s) were added to refs/heads/release-2.x by this push:
     new 4ef6933  Add missing package for binary compatibility
4ef6933 is described below

commit 4ef6933458dfd30cd4c45c68d4dd6527c5c2e059
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Mon Mar 21 19:27:52 2022 -0400

    Add missing package for binary compatibility
    
    - Note the package Javadoc: "This package lets you manage log4j settings
    using JMX. It is unfortunately not of production quality."
    
    There is no testing for behavioral compatibility, this is currently only
    to avoid class not found errors.
---
 .../org/apache/log4j/jmx/AbstractDynamicMBean.java | 177 +++++++++++++
 .../src/main/java/org/apache/log4j/jmx/Agent.java  | 131 ++++++++++
 .../org/apache/log4j/jmx/AppenderDynamicMBean.java | 280 +++++++++++++++++++++
 .../apache/log4j/jmx/HierarchyDynamicMBean.java    | 252 +++++++++++++++++++
 .../org/apache/log4j/jmx/LayoutDynamicMBean.java   | 216 ++++++++++++++++
 .../org/apache/log4j/jmx/LoggerDynamicMBean.java   | 227 +++++++++++++++++
 .../java/org/apache/log4j/jmx/MethodUnion.java     |  32 +++
 .../java/org/apache/log4j/jmx/package-info.java    |  20 ++
 8 files changed, 1335 insertions(+)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AbstractDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AbstractDynamicMBean.java
new file mode 100644
index 0000000..f4543fd
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AbstractDynamicMBean.java
@@ -0,0 +1,177 @@
+/*
+ * 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 java.util.Enumeration;
+import java.util.Vector;
+
+import javax.management.Attribute;
+import javax.management.AttributeList;
+import javax.management.DynamicMBean;
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.InstanceNotFoundException;
+import javax.management.JMException;
+import javax.management.MBeanRegistration;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+
+public abstract class AbstractDynamicMBean implements DynamicMBean, MBeanRegistration {
+
+    /**
+     * Get MBean name.
+     * 
+     * @param appender appender, may not be null.
+     * @return name.
+     * @since 1.2.16
+     */
+    static protected String getAppenderName(final Appender appender) {
+        String name = appender.getName();
+        if (name == null || name.trim().length() == 0) {
+            // try to get some form of a name, because null is not allowed (exception), and empty string certainly isn't useful in
+            // JMX..
+            name = appender.toString();
+        }
+        return name;
+    }
+    String dClassName;
+    MBeanServer server;
+
+    private final Vector mbeanList = new Vector();
+
+    /**
+     * Enables the to get the values of several attributes of the Dynamic MBean.
+     */
+    @Override
+    public AttributeList getAttributes(final String[] attributeNames) {
+
+        // Check attributeNames is not null to avoid NullPointerException later on
+        if (attributeNames == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("attributeNames[] cannot be null"), "Cannot invoke a getter of " + dClassName);
+        }
+
+        final AttributeList resultList = new AttributeList();
+
+        // if attributeNames is empty, return an empty result list
+        if (attributeNames.length == 0) {
+            return resultList;
+        }
+
+        // build the result attribute list
+        for (final String attributeName : attributeNames) {
+            try {
+                final Object value = getAttribute((String) attributeName);
+                resultList.add(new Attribute(attributeName, value));
+            } catch (final JMException e) {
+                e.printStackTrace();
+            } catch (final RuntimeException e) {
+                e.printStackTrace();
+            }
+        }
+        return (resultList);
+    }
+
+    protected abstract Logger getLogger();
+
+    @Override
+    public void postDeregister() {
+        getLogger().debug("postDeregister is called.");
+    }
+
+    @Override
+    public void postRegister(final java.lang.Boolean registrationDone) {
+    }
+
+    /**
+     * Performs cleanup for deregistering this MBean. Default implementation unregisters MBean instances which are
+     * registered using {@link #registerMBean(Object mbean, ObjectName objectName)}.
+     */
+    @Override
+    public void preDeregister() {
+        getLogger().debug("preDeregister called.");
+
+        final Enumeration iterator = mbeanList.elements();
+        while (iterator.hasMoreElements()) {
+            final ObjectName name = (ObjectName) iterator.nextElement();
+            try {
+                server.unregisterMBean(name);
+            } catch (final InstanceNotFoundException e) {
+                getLogger().warn("Missing MBean " + name.getCanonicalName());
+            } catch (final MBeanRegistrationException e) {
+                getLogger().warn("Failed unregistering " + name.getCanonicalName());
+            }
+        }
+    }
+
+    @Override
+    public ObjectName preRegister(final MBeanServer server, final ObjectName name) {
+        getLogger().debug("preRegister called. Server=" + server + ", name=" + name);
+        this.server = server;
+        return name;
+    }
+
+    /**
+     * Registers MBean instance in the attached server. Must <em>NOT</em> be called before registration of this instance.
+     */
+    protected void registerMBean(final Object mbean, final ObjectName objectName)
+        throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
+        server.registerMBean(mbean, objectName);
+        mbeanList.add(objectName);
+    }
+
+    /**
+     * Sets the values of several attributes of the Dynamic MBean, and returns the list of attributes that have been set.
+     */
+    @Override
+    public AttributeList setAttributes(final AttributeList attributes) {
+
+        // Check attributes is not null to avoid NullPointerException later on
+        if (attributes == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("AttributeList attributes cannot be null"),
+                "Cannot invoke a setter of " + dClassName);
+        }
+        final AttributeList resultList = new AttributeList();
+
+        // if attributeNames is empty, nothing more to do
+        if (attributes.isEmpty()) {
+            return resultList;
+        }
+
+        // for each attribute, try to set it and add to the result list if successfull
+        for (final Object attribute : attributes) {
+            final Attribute attr = (Attribute) attribute;
+            try {
+                setAttribute(attr);
+                final String name = attr.getName();
+                final Object value = getAttribute(name);
+                resultList.add(new Attribute(name, value));
+            } catch (final JMException e) {
+                e.printStackTrace();
+            } catch (final RuntimeException e) {
+                e.printStackTrace();
+            }
+        }
+        return (resultList);
+    }
+
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/Agent.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/Agent.java
new file mode 100644
index 0000000..e1cd358
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/Agent.java
@@ -0,0 +1,131 @@
+/*
+ * 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 java.io.InterruptedIOException;
+import java.lang.reflect.InvocationTargetException;
+
+import javax.management.JMException;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.ObjectName;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Manages an instance of com.sun.jdmk.comm.HtmlAdapterServer which was provided for demonstration purposes in the Java
+ * Management Extensions Reference Implementation 1.2.1. This class is provided to maintain compatibility with earlier
+ * versions of log4j and use in new code is discouraged.
+ *
+ * @deprecated
+ */
+@Deprecated
+public class Agent {
+
+    /**
+     * Diagnostic logger.
+     * 
+     * @deprecated
+     */
+    @Deprecated
+    static Logger log = Logger.getLogger(Agent.class);
+
+    /**
+     * Creates a new instance of com.sun.jdmk.comm.HtmlAdapterServer using reflection.
+     *
+     * @since 1.2.16
+     * @return new instance.
+     */
+    private static Object createServer() {
+        Object newInstance = null;
+        try {
+            newInstance = Class.forName("com.sun.jdmk.comm.HtmlAdapterServer").newInstance();
+        } catch (final ClassNotFoundException ex) {
+            throw new RuntimeException(ex.toString());
+        } catch (final InstantiationException ex) {
+            throw new RuntimeException(ex.toString());
+        } catch (final IllegalAccessException ex) {
+            throw new RuntimeException(ex.toString());
+        }
+        return newInstance;
+    }
+
+    /**
+     * Invokes HtmlAdapterServer.start() using reflection.
+     *
+     * @since 1.2.16
+     * @param server instance of com.sun.jdmk.comm.HtmlAdapterServer.
+     */
+    private static void startServer(final Object server) {
+        try {
+            server.getClass().getMethod("start", new Class[0]).invoke(server, new Object[0]);
+        } catch (final InvocationTargetException ex) {
+            final Throwable cause = ex.getTargetException();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            } else if (cause != null) {
+                if (cause instanceof InterruptedException || cause instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                throw new RuntimeException(cause.toString());
+            } else {
+                throw new RuntimeException();
+            }
+        } catch (final NoSuchMethodException ex) {
+            throw new RuntimeException(ex.toString());
+        } catch (final IllegalAccessException ex) {
+            throw new RuntimeException(ex.toString());
+        }
+    }
+
+    /**
+     * Create new instance.
+     * 
+     * @deprecated
+     */
+    @Deprecated
+    public Agent() {
+    }
+
+    /**
+     * Starts instance of HtmlAdapterServer.
+     * 
+     * @deprecated
+     */
+    @Deprecated
+    public void start() {
+
+        final MBeanServer server = MBeanServerFactory.createMBeanServer();
+        final Object html = createServer();
+
+        try {
+            log.info("Registering HtmlAdaptorServer instance.");
+            server.registerMBean(html, new ObjectName("Adaptor:name=html,port=8082"));
+            log.info("Registering HierarchyDynamicMBean instance.");
+            final HierarchyDynamicMBean hdm = new HierarchyDynamicMBean();
+            server.registerMBean(hdm, new ObjectName("log4j:hiearchy=default"));
+        } catch (final JMException e) {
+            log.error("Problem while registering MBeans instances.", e);
+            return;
+        } catch (final RuntimeException e) {
+            log.error("Problem while registering MBeans instances.", e);
+            return;
+        }
+        startServer(html);
+    }
+}
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
new file mode 100644
index 0000000..6374059
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/AppenderDynamicMBean.java
@@ -0,0 +1,280 @@
+/*
+ * 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 java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.InterruptedIOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Priority;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.OptionHandler;
+
+public class AppenderDynamicMBean extends AbstractDynamicMBean {
+
+    // This category instance is for logging.
+    private static Logger cat = Logger.getLogger(AppenderDynamicMBean.class);
+    private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+    private final Vector dAttributes = new Vector();
+
+    private final String dClassName = this.getClass().getName();
+    private final Hashtable dynamicProps = new Hashtable(5);
+    private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[2];
+
+    private final String dDescription = "This MBean acts as a management facade for log4j appenders.";
+
+    // We wrap this appender instance.
+    private final Appender appender;
+
+    public AppenderDynamicMBean(final Appender appender) throws IntrospectionException {
+        this.appender = appender;
+        buildDynamicMBeanInfo();
+    }
+
+    private void buildDynamicMBeanInfo() throws IntrospectionException {
+        final Constructor[] constructors = this.getClass().getConstructors();
+        dConstructors[0] = new MBeanConstructorInfo("AppenderDynamicMBean(): Constructs a AppenderDynamicMBean instance", constructors[0]);
+
+        final BeanInfo bi = Introspector.getBeanInfo(appender.getClass());
+        final PropertyDescriptor[] pd = bi.getPropertyDescriptors();
+
+        final int size = pd.length;
+
+        for (int i = 0; i < size; i++) {
+            final String name = pd[i].getName();
+            final Method readMethod = pd[i].getReadMethod();
+            final Method writeMethod = pd[i].getWriteMethod();
+            if (readMethod != null) {
+                final Class returnClass = readMethod.getReturnType();
+                if (isSupportedType(returnClass)) {
+                    String returnClassName;
+                    if (returnClass.isAssignableFrom(Priority.class)) {
+                        returnClassName = "java.lang.String";
+                    } else {
+                        returnClassName = returnClass.getName();
+                    }
+
+                    dAttributes.add(new MBeanAttributeInfo(name, returnClassName, "Dynamic", true, writeMethod != null, false));
+                    dynamicProps.put(name, new MethodUnion(readMethod, writeMethod));
+                }
+            }
+        }
+
+        MBeanParameterInfo[] params = new MBeanParameterInfo[0];
+
+        dOperations[0] = new MBeanOperationInfo("activateOptions", "activateOptions(): add an appender", params, "void", MBeanOperationInfo.ACTION);
+
+        params = new MBeanParameterInfo[1];
+        params[0] = new MBeanParameterInfo("layout class", "java.lang.String", "layout class");
+
+        dOperations[1] = new MBeanOperationInfo("setLayout", "setLayout(): add a layout", params, "void", MBeanOperationInfo.ACTION);
+    }
+
+    @Override
+    public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException {
+
+        // Check attributeName is not null to avoid NullPointerException later on
+        if (attributeName == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke a getter of " + dClassName + " with null attribute name");
+        }
+
+        cat.debug("getAttribute called with [" + attributeName + "].");
+        if (attributeName.startsWith("appender=" + appender.getName() + ",layout")) {
+            try {
+                return new ObjectName("log4j:" + attributeName);
+            } catch (final MalformedObjectNameException e) {
+                cat.error("attributeName", e);
+            } catch (final RuntimeException e) {
+                cat.error("attributeName", e);
+            }
+        }
+
+        final MethodUnion mu = (MethodUnion) dynamicProps.get(attributeName);
+
+        // cat.debug("----name="+attributeName+", b="+b);
+
+        if (mu != null && mu.readMethod != null) {
+            try {
+                return mu.readMethod.invoke(appender, null);
+            } catch (final IllegalAccessException e) {
+                return null;
+            } catch (final InvocationTargetException e) {
+                if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                return null;
+            } catch (final RuntimeException e) {
+                return null;
+            }
+        }
+
+        // If attributeName has not been recognized throw an AttributeNotFoundException
+        throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName));
+
+    }
+
+    @Override
+    protected Logger getLogger() {
+        return cat;
+    }
+
+    @Override
+    public MBeanInfo getMBeanInfo() {
+        cat.debug("getMBeanInfo called.");
+
+        final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()];
+        dAttributes.toArray(attribs);
+
+        return new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]);
+    }
+
+    @Override
+    public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException {
+
+        if (operationName.equals("activateOptions") && appender instanceof OptionHandler) {
+            final OptionHandler oh = (OptionHandler) appender;
+            oh.activateOptions();
+            return "Options activated.";
+        } else if (operationName.equals("setLayout")) {
+            final Layout layout = (Layout) OptionConverter.instantiateByClassName((String) params[0], Layout.class, null);
+            appender.setLayout(layout);
+            registerLayoutMBean(layout);
+        }
+        return null;
+    }
+
+    private boolean isSupportedType(final Class clazz) {
+        if (clazz.isPrimitive() || (clazz == String.class) || clazz.isAssignableFrom(Priority.class)) {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    @Override
+    public ObjectName preRegister(final MBeanServer server, final ObjectName name) {
+        cat.debug("preRegister called. Server=" + server + ", name=" + name);
+        this.server = server;
+        registerLayoutMBean(appender.getLayout());
+
+        return name;
+    }
+
+    void registerLayoutMBean(final Layout layout) {
+        if (layout == null) {
+            return;
+        }
+
+        final String name = getAppenderName(appender) + ",layout=" + layout.getClass().getName();
+        cat.debug("Adding LayoutMBean:" + name);
+        ObjectName objectName = null;
+        try {
+            final LayoutDynamicMBean appenderMBean = new LayoutDynamicMBean(layout);
+            objectName = new ObjectName("log4j:appender=" + name);
+            if (!server.isRegistered(objectName)) {
+                registerMBean(appenderMBean, objectName);
+                dAttributes.add(new MBeanAttributeInfo("appender=" + name, "javax.management.ObjectName", "The " + name + " layout.", true, true, false));
+            }
+
+        } catch (final JMException e) {
+            cat.error("Could not add DynamicLayoutMBean for [" + name + "].", e);
+        } catch (final java.beans.IntrospectionException e) {
+            cat.error("Could not add DynamicLayoutMBean for [" + name + "].", e);
+        } catch (final RuntimeException e) {
+            cat.error("Could not add DynamicLayoutMBean for [" + name + "].", e);
+        }
+    }
+
+    @Override
+    public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
+
+        // Check attribute is not null to avoid NullPointerException later on
+        if (attribute == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"),
+                "Cannot invoke a setter of " + dClassName + " with null attribute");
+        }
+        final String name = attribute.getName();
+        Object value = attribute.getValue();
+
+        if (name == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke the setter of " + dClassName + " with null attribute name");
+        }
+
+        final MethodUnion mu = (MethodUnion) dynamicProps.get(name);
+
+        if (mu != null && mu.writeMethod != null) {
+            final Object[] o = new Object[1];
+
+            final Class[] params = mu.writeMethod.getParameterTypes();
+            if (params[0] == org.apache.log4j.Priority.class) {
+                value = OptionConverter.toLevel((String) value, (Level) getAttribute(name));
+            }
+            o[0] = value;
+
+            try {
+                mu.writeMethod.invoke(appender, o);
+
+            } catch (final InvocationTargetException e) {
+                if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                cat.error("FIXME", e);
+            } catch (final IllegalAccessException e) {
+                cat.error("FIXME", e);
+            } catch (final RuntimeException e) {
+                cat.error("FIXME", e);
+            }
+        } else if (name.endsWith(".layout")) {
+
+        } else {
+            throw (new AttributeNotFoundException("Attribute " + name + " not found in " + this.getClass().getName()));
+        }
+    }
+
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/HierarchyDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/HierarchyDynamicMBean.java
new file mode 100644
index 0000000..b29ef8b
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/HierarchyDynamicMBean.java
@@ -0,0 +1,252 @@
+/*
+ * 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 java.lang.reflect.Constructor;
+import java.util.Vector;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMException;
+import javax.management.ListenerNotFoundException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.Notification;
+import javax.management.NotificationBroadcaster;
+import javax.management.NotificationBroadcasterSupport;
+import javax.management.NotificationFilter;
+import javax.management.NotificationFilterSupport;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.HierarchyEventListener;
+import org.apache.log4j.spi.LoggerRepository;
+
+public class HierarchyDynamicMBean extends AbstractDynamicMBean implements HierarchyEventListener, NotificationBroadcaster {
+
+    static final String ADD_APPENDER = "addAppender.";
+    static final String THRESHOLD = "threshold";
+
+    private static Logger log = Logger.getLogger(HierarchyDynamicMBean.class);
+    private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+
+    private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];
+    private final Vector vAttributes = new Vector();
+    private final String dClassName = this.getClass().getName();
+
+    private final String dDescription = "This MBean acts as a management facade for org.apache.log4j.Hierarchy.";
+
+    private final NotificationBroadcasterSupport nbs = new NotificationBroadcasterSupport();
+
+    private final LoggerRepository hierarchy;
+
+    public HierarchyDynamicMBean() {
+        hierarchy = LogManager.getLoggerRepository();
+        buildDynamicMBeanInfo();
+    }
+
+    @Override
+    public void addAppenderEvent(final Category logger, final Appender appender) {
+        log.debug("addAppenderEvent called: logger=" + logger.getName() + ", appender=" + appender.getName());
+        final Notification n = new Notification(ADD_APPENDER + logger.getName(), this, 0);
+        n.setUserData(appender);
+        log.debug("sending notification.");
+        nbs.sendNotification(n);
+    }
+
+    ObjectName addLoggerMBean(final Logger logger) {
+        final String name = logger.getName();
+        ObjectName objectName = null;
+        try {
+            final LoggerDynamicMBean loggerMBean = new LoggerDynamicMBean(logger);
+            objectName = new ObjectName("log4j", "logger", name);
+
+            if (!server.isRegistered(objectName)) {
+                registerMBean(loggerMBean, objectName);
+                final NotificationFilterSupport nfs = new NotificationFilterSupport();
+                nfs.enableType(ADD_APPENDER + logger.getName());
+                log.debug("---Adding logger [" + name + "] as listener.");
+                nbs.addNotificationListener(loggerMBean, nfs, null);
+                vAttributes.add(new MBeanAttributeInfo("logger=" + name, "javax.management.ObjectName", "The " + name + " logger.", true, true, // this makes
+                                                                                                                                                // the object
+                    // clickable
+                    false));
+
+            }
+
+        } catch (final JMException e) {
+            log.error("Could not add loggerMBean for [" + name + "].", e);
+        } catch (final RuntimeException e) {
+            log.error("Could not add loggerMBean for [" + name + "].", e);
+        }
+        return objectName;
+    }
+
+    public ObjectName addLoggerMBean(final String name) {
+        final Logger cat = LogManager.exists(name);
+
+        if (cat != null) {
+            return addLoggerMBean(cat);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void addNotificationListener(final NotificationListener listener, final NotificationFilter filter, final java.lang.Object handback) {
+        nbs.addNotificationListener(listener, filter, handback);
+    }
+
+    private void buildDynamicMBeanInfo() {
+        final Constructor[] constructors = this.getClass().getConstructors();
+        dConstructors[0] = new MBeanConstructorInfo("HierarchyDynamicMBean(): Constructs a HierarchyDynamicMBean instance", constructors[0]);
+
+        vAttributes.add(new MBeanAttributeInfo(THRESHOLD, "java.lang.String", "The \"threshold\" state of the hiearchy.", true, true, false));
+
+        final MBeanParameterInfo[] params = new MBeanParameterInfo[1];
+        params[0] = new MBeanParameterInfo("name", "java.lang.String", "Create a logger MBean");
+        dOperations[0] = new MBeanOperationInfo("addLoggerMBean", "addLoggerMBean(): add a loggerMBean", params, "javax.management.ObjectName",
+            MBeanOperationInfo.ACTION);
+    }
+
+    @Override
+    public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException {
+
+        // Check attributeName is not null to avoid NullPointerException later on
+        if (attributeName == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke a getter of " + dClassName + " with null attribute name");
+        }
+
+        log.debug("Called getAttribute with [" + attributeName + "].");
+
+        // Check for a recognized attributeName and call the corresponding getter
+        if (attributeName.equals(THRESHOLD)) {
+            return hierarchy.getThreshold();
+        } else if (attributeName.startsWith("logger")) {
+            final int k = attributeName.indexOf("%3D");
+            String val = attributeName;
+            if (k > 0) {
+                val = attributeName.substring(0, k) + '=' + attributeName.substring(k + 3);
+            }
+            try {
+                return new ObjectName("log4j:" + val);
+            } catch (final JMException e) {
+                log.error("Could not create ObjectName" + val);
+            } catch (final RuntimeException e) {
+                log.error("Could not create ObjectName" + val);
+            }
+        }
+
+        // If attributeName has not been recognized throw an AttributeNotFoundException
+        throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName));
+
+    }
+
+    @Override
+    protected Logger getLogger() {
+        return log;
+    }
+
+    @Override
+    public MBeanInfo getMBeanInfo() {
+        // cat.debug("getMBeanInfo called.");
+
+        final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[vAttributes.size()];
+        vAttributes.toArray(attribs);
+
+        return new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]);
+    }
+
+    @Override
+    public MBeanNotificationInfo[] getNotificationInfo() {
+        return nbs.getNotificationInfo();
+    }
+
+    @Override
+    public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException {
+
+        if (operationName == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Operation name cannot be null"),
+                "Cannot invoke a null operation in " + dClassName);
+        }
+        // Check for a recognized operation name and call the corresponding operation
+
+        if (operationName.equals("addLoggerMBean")) {
+            return addLoggerMBean((String) params[0]);
+        } else {
+            throw new ReflectionException(new NoSuchMethodException(operationName), "Cannot find the operation " + operationName + " in " + dClassName);
+        }
+
+    }
+
+    @Override
+    public void postRegister(final java.lang.Boolean registrationDone) {
+        log.debug("postRegister is called.");
+        hierarchy.addHierarchyEventListener(this);
+        final Logger root = hierarchy.getRootLogger();
+        addLoggerMBean(root);
+    }
+
+    @Override
+    public void removeAppenderEvent(final Category cat, final Appender appender) {
+        log.debug("removeAppenderCalled: logger=" + cat.getName() + ", appender=" + appender.getName());
+    }
+
+    @Override
+    public void removeNotificationListener(final NotificationListener listener) throws ListenerNotFoundException {
+        nbs.removeNotificationListener(listener);
+    }
+
+    @Override
+    public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
+
+        // Check attribute is not null to avoid NullPointerException later on
+        if (attribute == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"),
+                "Cannot invoke a setter of " + dClassName + " with null attribute");
+        }
+        final String name = attribute.getName();
+        final Object value = attribute.getValue();
+
+        if (name == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke the setter of " + dClassName + " with null attribute name");
+        }
+
+        if (name.equals(THRESHOLD)) {
+            final Level l = OptionConverter.toLevel((String) value, hierarchy.getThreshold());
+            hierarchy.setThreshold(l);
+        }
+
+    }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LayoutDynamicMBean.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LayoutDynamicMBean.java
new file mode 100644
index 0000000..68e9ebf
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LayoutDynamicMBean.java
@@ -0,0 +1,216 @@
+/*
+ * 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 java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.InterruptedIOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.OptionHandler;
+
+public class LayoutDynamicMBean extends AbstractDynamicMBean {
+
+    // This category instance is for logging.
+    private static Logger cat = Logger.getLogger(LayoutDynamicMBean.class);
+    private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+    private final Vector dAttributes = new Vector();
+
+    private final String dClassName = this.getClass().getName();
+    private final Hashtable dynamicProps = new Hashtable(5);
+    private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];
+
+    private final String dDescription = "This MBean acts as a management facade for log4j layouts.";
+
+    // We wrap this layout instance.
+    private final Layout layout;
+
+    public LayoutDynamicMBean(final Layout layout) throws IntrospectionException {
+        this.layout = layout;
+        buildDynamicMBeanInfo();
+    }
+
+    private void buildDynamicMBeanInfo() throws IntrospectionException {
+        final Constructor[] constructors = this.getClass().getConstructors();
+        dConstructors[0] = new MBeanConstructorInfo("LayoutDynamicMBean(): Constructs a LayoutDynamicMBean instance", constructors[0]);
+
+        final BeanInfo bi = Introspector.getBeanInfo(layout.getClass());
+        final PropertyDescriptor[] pd = bi.getPropertyDescriptors();
+
+        final int size = pd.length;
+
+        for (int i = 0; i < size; i++) {
+            final String name = pd[i].getName();
+            final Method readMethod = pd[i].getReadMethod();
+            final Method writeMethod = pd[i].getWriteMethod();
+            if (readMethod != null) {
+                final Class returnClass = readMethod.getReturnType();
+                if (isSupportedType(returnClass)) {
+                    String returnClassName;
+                    if (returnClass.isAssignableFrom(Level.class)) {
+                        returnClassName = "java.lang.String";
+                    } else {
+                        returnClassName = returnClass.getName();
+                    }
+
+                    dAttributes.add(new MBeanAttributeInfo(name, returnClassName, "Dynamic", true, writeMethod != null, false));
+                    dynamicProps.put(name, new MethodUnion(readMethod, writeMethod));
+                }
+            }
+        }
+
+        final MBeanParameterInfo[] params = new MBeanParameterInfo[0];
+
+        dOperations[0] = new MBeanOperationInfo("activateOptions", "activateOptions(): add an layout", params, "void", MBeanOperationInfo.ACTION);
+    }
+
+    @Override
+    public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException {
+
+        // Check attributeName is not null to avoid NullPointerException later on
+        if (attributeName == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke a getter of " + dClassName + " with null attribute name");
+        }
+
+        final MethodUnion mu = (MethodUnion) dynamicProps.get(attributeName);
+
+        cat.debug("----name=" + attributeName + ", mu=" + mu);
+
+        if (mu != null && mu.readMethod != null) {
+            try {
+                return mu.readMethod.invoke(layout, null);
+            } catch (final InvocationTargetException e) {
+                if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                return null;
+            } catch (final IllegalAccessException e) {
+                return null;
+            } catch (final RuntimeException e) {
+                return null;
+            }
+        }
+
+        // If attributeName has not been recognized throw an AttributeNotFoundException
+        throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName));
+
+    }
+
+    @Override
+    protected Logger getLogger() {
+        return cat;
+    }
+
+    @Override
+    public MBeanInfo getMBeanInfo() {
+        cat.debug("getMBeanInfo called.");
+
+        final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()];
+        dAttributes.toArray(attribs);
+
+        return new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]);
+    }
+
+    @Override
+    public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException {
+
+        if (operationName.equals("activateOptions") && layout instanceof OptionHandler) {
+            final OptionHandler oh = (OptionHandler) layout;
+            oh.activateOptions();
+            return "Options activated.";
+        }
+        return null;
+    }
+
+    private boolean isSupportedType(final Class clazz) {
+        if (clazz.isPrimitive() || (clazz == String.class) || clazz.isAssignableFrom(Level.class)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
+
+        // Check attribute is not null to avoid NullPointerException later on
+        if (attribute == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"),
+                "Cannot invoke a setter of " + dClassName + " with null attribute");
+        }
+        final String name = attribute.getName();
+        Object value = attribute.getValue();
+
+        if (name == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke the setter of " + dClassName + " with null attribute name");
+        }
+
+        final MethodUnion mu = (MethodUnion) dynamicProps.get(name);
+
+        if (mu != null && mu.writeMethod != null) {
+            final Object[] o = new Object[1];
+
+            final Class[] params = mu.writeMethod.getParameterTypes();
+            if (params[0] == org.apache.log4j.Priority.class) {
+                value = OptionConverter.toLevel((String) value, (Level) getAttribute(name));
+            }
+            o[0] = value;
+
+            try {
+                mu.writeMethod.invoke(layout, o);
+
+            } catch (final InvocationTargetException e) {
+                if (e.getTargetException() instanceof InterruptedException || e.getTargetException() instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+                }
+                cat.error("FIXME", e);
+            } catch (final IllegalAccessException e) {
+                cat.error("FIXME", e);
+            } catch (final RuntimeException e) {
+                cat.error("FIXME", e);
+            }
+        } else {
+            throw (new AttributeNotFoundException("Attribute " + name + " not found in " + this.getClass().getName()));
+        }
+    }
+}
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
new file mode 100644
index 0000000..4ae0e56
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/LoggerDynamicMBean.java
@@ -0,0 +1,227 @@
+/*
+ * 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 java.lang.reflect.Constructor;
+import java.util.Enumeration;
+import java.util.Vector;
+
+import javax.management.Attribute;
+import javax.management.AttributeNotFoundException;
+import javax.management.InvalidAttributeValueException;
+import javax.management.JMException;
+import javax.management.MBeanAttributeInfo;
+import javax.management.MBeanConstructorInfo;
+import javax.management.MBeanException;
+import javax.management.MBeanInfo;
+import javax.management.MBeanNotificationInfo;
+import javax.management.MBeanOperationInfo;
+import javax.management.MBeanParameterInfo;
+import javax.management.MalformedObjectNameException;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+import javax.management.ReflectionException;
+import javax.management.RuntimeOperationsException;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.helpers.OptionConverter;
+
+public class LoggerDynamicMBean extends AbstractDynamicMBean implements NotificationListener {
+
+    // This Logger instance is for logging.
+    private static Logger cat = Logger.getLogger(LoggerDynamicMBean.class);
+    private final MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
+
+    private final MBeanOperationInfo[] dOperations = new MBeanOperationInfo[1];
+    private final Vector dAttributes = new Vector();
+
+    private final String dClassName = this.getClass().getName();
+
+    private final String dDescription = "This MBean acts as a management facade for a org.apache.log4j.Logger instance.";
+
+    // We wrap this Logger instance.
+    private final Logger logger;
+
+    public LoggerDynamicMBean(final Logger logger) {
+        this.logger = logger;
+        buildDynamicMBeanInfo();
+    }
+
+    void addAppender(final String appenderClass, final String appenderName) {
+        cat.debug("addAppender called with " + appenderClass + ", " + appenderName);
+        final Appender appender = (Appender) OptionConverter.instantiateByClassName(appenderClass, org.apache.log4j.Appender.class, null);
+        appender.setName(appenderName);
+        logger.addAppender(appender);
+
+        // appenderMBeanRegistration();
+
+    }
+
+    void appenderMBeanRegistration() {
+        final Enumeration enumeration = logger.getAllAppenders();
+        while (enumeration.hasMoreElements()) {
+            final Appender appender = (Appender) enumeration.nextElement();
+            registerAppenderMBean(appender);
+        }
+    }
+
+    private void buildDynamicMBeanInfo() {
+        final Constructor[] constructors = this.getClass().getConstructors();
+        dConstructors[0] = new MBeanConstructorInfo("HierarchyDynamicMBean(): Constructs a HierarchyDynamicMBean instance", constructors[0]);
+
+        dAttributes.add(new MBeanAttributeInfo("name", "java.lang.String", "The name of this Logger.", true, false, false));
+
+        dAttributes.add(new MBeanAttributeInfo("priority", "java.lang.String", "The priority of this logger.", true, true, false));
+
+        final MBeanParameterInfo[] params = new MBeanParameterInfo[2];
+        params[0] = new MBeanParameterInfo("class name", "java.lang.String", "add an appender to this logger");
+        params[1] = new MBeanParameterInfo("appender name", "java.lang.String", "name of the appender");
+
+        dOperations[0] = new MBeanOperationInfo("addAppender", "addAppender(): add an appender", params, "void", MBeanOperationInfo.ACTION);
+    }
+
+    @Override
+    public Object getAttribute(final String attributeName) throws AttributeNotFoundException, MBeanException, ReflectionException {
+
+        // Check attributeName is not null to avoid NullPointerException later on
+        if (attributeName == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke a getter of " + dClassName + " with null attribute name");
+        }
+
+        // Check for a recognized attributeName and call the corresponding getter
+        if (attributeName.equals("name")) {
+            return logger.getName();
+        } else if (attributeName.equals("priority")) {
+            final Level l = logger.getLevel();
+            if (l == null) {
+                return null;
+            } else {
+                return l.toString();
+            }
+        } else if (attributeName.startsWith("appender=")) {
+            try {
+                return new ObjectName("log4j:" + attributeName);
+            } catch (final MalformedObjectNameException e) {
+                cat.error("Could not create ObjectName" + attributeName);
+            } catch (final RuntimeException e) {
+                cat.error("Could not create ObjectName" + attributeName);
+            }
+        }
+
+        // If attributeName has not been recognized throw an AttributeNotFoundException
+        throw (new AttributeNotFoundException("Cannot find " + attributeName + " attribute in " + dClassName));
+
+    }
+
+    @Override
+    protected Logger getLogger() {
+        return logger;
+    }
+
+    @Override
+    public MBeanInfo getMBeanInfo() {
+        // cat.debug("getMBeanInfo called.");
+
+        final MBeanAttributeInfo[] attribs = new MBeanAttributeInfo[dAttributes.size()];
+        dAttributes.toArray(attribs);
+
+        final MBeanInfo mb = new MBeanInfo(dClassName, dDescription, attribs, dConstructors, dOperations, new MBeanNotificationInfo[0]);
+        // cat.debug("getMBeanInfo exit.");
+        return mb;
+    }
+
+    @Override
+    public void handleNotification(final Notification notification, final Object handback) {
+        cat.debug("Received notification: " + notification.getType());
+        registerAppenderMBean((Appender) notification.getUserData());
+
+    }
+
+    @Override
+    public Object invoke(final String operationName, final Object params[], final String signature[]) throws MBeanException, ReflectionException {
+
+        if (operationName.equals("addAppender")) {
+            addAppender((String) params[0], (String) params[1]);
+            return "Hello world.";
+        }
+
+        return null;
+    }
+
+    @Override
+    public void postRegister(final java.lang.Boolean registrationDone) {
+        appenderMBeanRegistration();
+    }
+
+    void registerAppenderMBean(final Appender appender) {
+        final String name = getAppenderName(appender);
+        cat.debug("Adding AppenderMBean for appender named " + name);
+        ObjectName objectName = null;
+        try {
+            final AppenderDynamicMBean appenderMBean = new AppenderDynamicMBean(appender);
+            objectName = new ObjectName("log4j", "appender", name);
+            if (!server.isRegistered(objectName)) {
+                registerMBean(appenderMBean, objectName);
+                dAttributes.add(new MBeanAttributeInfo("appender=" + name, "javax.management.ObjectName", "The " + name + " appender.", true, true, false));
+            }
+
+        } catch (final JMException e) {
+            cat.error("Could not add appenderMBean for [" + name + "].", e);
+        } catch (final java.beans.IntrospectionException e) {
+            cat.error("Could not add appenderMBean for [" + name + "].", e);
+        } catch (final RuntimeException e) {
+            cat.error("Could not add appenderMBean for [" + name + "].", e);
+        }
+    }
+
+    @Override
+    public void setAttribute(final Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
+
+        // Check attribute is not null to avoid NullPointerException later on
+        if (attribute == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute cannot be null"),
+                "Cannot invoke a setter of " + dClassName + " with null attribute");
+        }
+        final String name = attribute.getName();
+        final Object value = attribute.getValue();
+
+        if (name == null) {
+            throw new RuntimeOperationsException(new IllegalArgumentException("Attribute name cannot be null"),
+                "Cannot invoke the setter of " + dClassName + " with null attribute name");
+        }
+
+        if (name.equals("priority")) {
+            if (value instanceof String) {
+                final String s = (String) value;
+                Level p = logger.getLevel();
+                if (s.equalsIgnoreCase("NULL")) {
+                    p = null;
+                } else {
+                    p = OptionConverter.toLevel(s, p);
+                }
+                logger.setLevel(p);
+            }
+        } else {
+            throw (new AttributeNotFoundException("Attribute " + name + " not found in " + this.getClass().getName()));
+        }
+    }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/MethodUnion.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/MethodUnion.java
new file mode 100644
index 0000000..4e57aec
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/MethodUnion.java
@@ -0,0 +1,32 @@
+/*
+ * 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 java.lang.reflect.Method;
+
+class MethodUnion {
+
+    Method readMethod;
+    Method writeMethod;
+
+    MethodUnion(final Method readMethod, final Method writeMethod) {
+        this.readMethod = readMethod;
+        this.writeMethod = writeMethod;
+    }
+
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/package-info.java b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/package-info.java
new file mode 100644
index 0000000..6eec100
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/jmx/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+/**
+ * This package lets you manage log4j settings using JMX. It is unfortunately not of production quality.
+ */
+package org.apache.log4j.jmx;