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/01/14 18:33:16 UTC

[logging-log4j2] 02/02: Implement configuration APIs in and PropertyConfigurator and BasicConfigurator.

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

commit 73a2cd1cd0e94c7f4f36e4ac9dc72380d30750ef
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Jan 14 13:33:10 2022 -0500

    Implement configuration APIs in and PropertyConfigurator and
    BasicConfigurator.
    
    - Call sites of public APIs now pick up the proper logger context based
    on the call sites' class loader.
    - Drop package private interface LoggerRepository2.
    - Can't break up this commit into smaller digestible chunks.
---
 .../java/org/apache/log4j/BasicConfigurator.java   |  32 +-
 .../src/main/java/org/apache/log4j/Category.java   | 704 ++++++++++++---------
 .../src/main/java/org/apache/log4j/Hierarchy.java  | 143 +++--
 .../src/main/java/org/apache/log4j/LogManager.java | 105 ++-
 .../java/org/apache/log4j/LoggerRepository2.java   |  31 -
 .../org/apache/log4j/PropertyConfigurator.java     | 603 +++++++++++++++++-
 .../log4j/config/PropertiesConfiguration.java      | 272 ++++----
 .../org/apache/log4j/BasicConfiguratorTest.java    |  56 ++
 .../org/apache/log4j/PropertyConfiguratorTest.java | 427 +++++++++++++
 9 files changed, 1790 insertions(+), 583 deletions(-)

diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java
index da90e2c..14910d8 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/BasicConfigurator.java
@@ -16,27 +16,45 @@
  */
 package org.apache.log4j;
 
+import org.apache.logging.log4j.util.StackLocatorUtil;
+
 /**
- * Provided for compatibility with Log4j 1.x.
- *
+ * Configures the package.
+ * 
+ * <p>
+ * For file based configuration, see {@link PropertyConfigurator}. For XML based configuration, see
+ * {@link org.apache.log4j.xml.DOMConfigurator DOMConfigurator}.
+ * </p>
+ * 
  * @since 0.8.1
  */
 public class BasicConfigurator {
 
+    /**
+     * Adds a {@link ConsoleAppender} that uses {@link PatternLayout} using the
+     * {@link PatternLayout#TTCC_CONVERSION_PATTERN} and prints to <code>System.out</code> to the root category.
+     */
     public static void configure() {
-        LogManager.reconfigure();
+        LogManager.reconfigure(StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * No-op implementation.
-     * @param appender The appender.
+     * Adds <code>appender</code> to the root category.
+     *
+     * @param appender The appender to add to the root category.
      */
     public static void configure(final Appender appender) {
-        // no-op
+        LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2)).addAppender(appender);
     }
 
+    /**
+     * Resets the default hierarchy to its default. It is equivalent to calling
+     * <code>Category.getDefaultHierarchy().resetConfiguration()</code>.
+     *
+     * See {@link Hierarchy#resetConfiguration()} for more details.
+     */
     public static void resetConfiguration() {
-        LogManager.resetConfiguration();
+        LogManager.resetConfiguration(StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java
index 286ae32..90857cb 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java
@@ -19,13 +19,17 @@ package org.apache.log4j;
 import java.util.Enumeration;
 import java.util.Map;
 import java.util.ResourceBundle;
+import java.util.Vector;
 import java.util.concurrent.ConcurrentMap;
 
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.helpers.AppenderAttachableImpl;
 import org.apache.log4j.helpers.NullEnumeration;
 import org.apache.log4j.legacy.core.CategoryUtil;
 import org.apache.log4j.or.ObjectRenderer;
 import org.apache.log4j.or.RendererMap;
 import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.HierarchyEventListener;
 import org.apache.log4j.spi.LoggerRepository;
 import org.apache.log4j.spi.LoggingEvent;
 import org.apache.logging.log4j.message.LocalizedMessage;
@@ -46,6 +50,78 @@ public class Category implements AppenderAttachable {
     private static final String FQCN = Category.class.getName();
 
     /**
+     * Tests if the named category exists (in the default hierarchy).
+     *
+     * @param name The name to test.
+     * @return Whether the name exists.
+     *
+     * @deprecated Please use {@link LogManager#exists(String)} instead.
+     * @since 0.8.5
+     */
+    @Deprecated
+    public static Logger exists(final String name) {
+        return LogManager.exists(name, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
+     * Returns all the currently defined categories in the default hierarchy as an {@link java.util.Enumeration
+     * Enumeration}.
+     *
+     * <p>
+     * The root category is <em>not</em> included in the returned {@link Enumeration}.
+     * </p>
+     *
+     * @return and Enumeration of the Categories.
+     *
+     * @deprecated Please use {@link LogManager#getCurrentLoggers()} instead.
+     */
+    @SuppressWarnings("rawtypes")
+    @Deprecated
+    public static Enumeration getCurrentCategories() {
+        return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
+     * Gets the default LoggerRepository instance.
+     *
+     * @return the default LoggerRepository instance.
+     * @deprecated Please use {@link LogManager#getLoggerRepository()} instead.
+     * @since 1.0
+     */
+    @Deprecated
+    public static LoggerRepository getDefaultHierarchy() {
+        return LogManager.getLoggerRepository();
+    }
+
+    public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) {
+        return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    public static Category getInstance(final String name) {
+        return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    public static Category getRoot() {
+        return LogManager.getRootLogger(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    private static String getSubName(final String name) {
+        if (Strings.isEmpty(name)) {
+            return null;
+        }
+        final int i = name.lastIndexOf('.');
+        return i > 0 ? name.substring(0, i) : Strings.EMPTY;
+    }
+
+    /**
+     * Shuts down the current configuration.
+     */
+    public static void shutdown() {
+        // Depth 2 gets the call site of this method.
+        LogManager.shutdown(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
      * The name of this category.
      */
     protected String name;
@@ -81,8 +157,11 @@ public class Category implements AppenderAttachable {
     /** Categories need to know what Hierarchy they are in. */
     protected LoggerRepository repository;
 
+    AppenderAttachableImpl aai;
+
     /**
      * Constructor used by Logger to specify a LoggerContext.
+     *
      * @param context The LoggerContext.
      * @param name The name of the Logger.
      */
@@ -90,158 +169,101 @@ public class Category implements AppenderAttachable {
         this.name = name;
         this.logger = context.getLogger(name);
         this.repository = LogManager.getLoggerRepository();
-        //this.rendererMap = ((RendererSupport) repository).getRendererMap();
+        // this.rendererMap = ((RendererSupport) repository).getRendererMap();
+    }
+
+    Category(final org.apache.logging.log4j.Logger logger) {
+        this.logger = logger;
+        // rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
     }
 
     /**
      * Constructor exposed by Log4j 1.2.
+     *
      * @param name The name of the Logger.
      */
     protected Category(final String name) {
         this(Hierarchy.getContext(), name);
     }
 
-    Category(final org.apache.logging.log4j.Logger logger) {
-        this.logger = logger;
-        //rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
-    }
-
-    public static Category getInstance(final String name) {
-        // Depth 2 gets the call site of this method.
-        return LogManager.getLogger(name, StackLocatorUtil.getCallerClassLoader(2));
-    }
-
-    public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) {
-        // Depth 2 gets the call site of this method.
-        return LogManager.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2));
-    }
-
-    public final String getName() {
-        return logger.getName();
-    }
-
-    org.apache.logging.log4j.Logger getLogger() {
-        return logger;
-    }
-
-    public final Category getParent() {
-        if (!LogManager.isLog4jCorePresent()) {
-            return null;
+    /**
+     * Add <code>newAppender</code> to the list of appenders of this Category instance.
+     * <p>
+     * If <code>newAppender</code> is already in the list of appenders, then it won't be added again.
+     * </p>
+     */
+    @Override
+    public void addAppender(final Appender appender) {
+        if (aai == null) {
+            aai = new AppenderAttachableImpl();
         }
-        final org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger);
-        final LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger);
-        if (parent == null || loggerContext == null) {
-            return null;
+        aai.addAppender(appender);
+        if (appender != null) {
+            repository.fireAddAppenderEvent(this, appender);
         }
-        final ConcurrentMap<String, Logger> loggers = Hierarchy.getLoggersMap(loggerContext);
-        final Logger parentLogger = loggers.get(parent.getName());
-        return parentLogger == null ? new Category(parent) : parentLogger;
-    }
-
-    public static Category getRoot() {
-        return getInstance(Strings.EMPTY);
     }
 
     /**
-     * Returns all the currently defined categories in the default hierarchy as an
-     * {@link java.util.Enumeration Enumeration}.
+     * If <code>assertion</code> parameter is {@code false}, then logs <code>msg</code> as an {@link #error(Object) error}
+     * statement.
      *
      * <p>
-     * The root category is <em>not</em> included in the returned
-     * {@link Enumeration}.
+     * The <code>assert</code> method has been renamed to <code>assertLog</code> because <code>assert</code> is a language
+     * reserved word in JDK 1.4.
      * </p>
      *
-     * @return and Enumeration of the Categories.
-     *
-     * @deprecated Please use {@link LogManager#getCurrentLoggers()} instead.
-     */
-    @SuppressWarnings("rawtypes")
-    @Deprecated
-    public static Enumeration getCurrentCategories() {
-        return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2));
-    }
-
-    /**
-     * Gets the default LoggerRepository instance.
+     * @param assertion The assertion.
+     * @param msg The message to print if <code>assertion</code> is false.
      *
-     * @return the default LoggerRepository instance.
-     * @deprecated Please use {@link LogManager#getLoggerRepository()} instead.
-     * @since 1.0
+     * @since 1.2
      */
-    @Deprecated
-    public static LoggerRepository getDefaultHierarchy() {
-        return LogManager.getLoggerRepository();
-    }
-
-    public Level getEffectiveLevel() {
-        switch (logger.getLevel().getStandardLevel()) {
-        case ALL:
-            return Level.ALL;
-        case TRACE:
-            return Level.TRACE;
-        case DEBUG:
-            return Level.DEBUG;
-        case INFO:
-            return Level.INFO;
-        case WARN:
-            return Level.WARN;
-        case ERROR:
-            return Level.ERROR;
-        case FATAL:
-            return Level.FATAL;
-        default:
-            // TODO Should this be an IllegalStateException?
-            return Level.OFF;
+    public void assertLog(final boolean assertion, final String msg) {
+        if (!assertion) {
+            this.error(msg);
         }
     }
 
     /**
-     * Gets the the {@link LoggerRepository} where this <code>Category</code> instance is attached.
+     * Call the appenders in the hierrachy starting at <code>this</code>. If no appenders could be found, emit a warning.
+     * <p>
+     * This method calls all the appenders inherited from the hierarchy circumventing any evaluation of whether to log or
+     * not to log the particular log request.
+     * </p>
      *
-     * @deprecated Please use {@link #getLoggerRepository()} instead.
-     * @since 1.1
+     * @param event the event to log.
      */
-    @Deprecated
-    public LoggerRepository getHierarchy() {
-        return repository;
+    public void callAppenders(final LoggingEvent event) {
+        int writes = 0;
+        for (Category c = this; c != null; c = c.parent) {
+            // Protected against simultaneous call to addAppender, removeAppender,...
+            synchronized (c) {
+                if (c.aai != null) {
+                    writes += c.aai.appendLoopOnAppenders(event);
+                }
+                if (!c.additive) {
+                    break;
+                }
+            }
+        }
+        if (writes == 0) {
+            repository.emitNoAppenderWarning(this);
+        }
     }
 
     /**
-     * Gets the the {@link LoggerRepository} where this <code>Category</code> is attached.
+     * Closes all attached appenders implementing the AppenderAttachable interface.
      *
-     * @since 1.2
+     * @since 1.0
      */
-    public LoggerRepository getLoggerRepository() {
-        return repository;
-    }
-
-    public Priority getChainedPriority() {
-        return getEffectiveLevel();
-    }
-
-    public final Level getLevel() {
-        return getEffectiveLevel();
-    }
-
-    private String getLevelStr(final Priority priority) {
-        return priority == null ? null : priority.levelStr;
-    }
-
-    public void setLevel(final Level level) {
-        setLevel(getLevelStr(level));
-    }
-
-    public final Level getPriority() {
-        return getEffectiveLevel();
-    }
-
-    public void setPriority(final Priority priority) {
-        setLevel(getLevelStr(priority));
-    }
-
-    private void setLevel(final String levelStr) {
-        if (LogManager.isLog4jCorePresent()) {
-            CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr));
+    synchronized void closeNestedAppenders() {
+        final Enumeration enumeration = this.getAllAppenders();
+        if (enumeration != null) {
+            while (enumeration.hasMoreElements()) {
+                final Appender a = (Appender) enumeration.nextElement();
+                if (a instanceof AppenderAttachable) {
+                    a.close();
+                }
+            }
         }
     }
 
@@ -253,10 +275,6 @@ public class Category implements AppenderAttachable {
         maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, t);
     }
 
-    public boolean isDebugEnabled() {
-        return logger.isDebugEnabled();
-    }
-
     public void error(final Object message) {
         maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, null);
     }
@@ -265,22 +283,6 @@ public class Category implements AppenderAttachable {
         maybeLog(FQCN, org.apache.logging.log4j.Level.ERROR, message, t);
     }
 
-    public boolean isErrorEnabled() {
-        return logger.isErrorEnabled();
-    }
-
-    public void warn(final Object message) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null);
-    }
-
-    public void warn(final Object message, final Throwable t) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t);
-    }
-
-    public boolean isWarnEnabled() {
-        return logger.isWarnEnabled();
-    }
-
     public void fatal(final Object message) {
         maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, null);
     }
@@ -289,163 +291,164 @@ public class Category implements AppenderAttachable {
         maybeLog(FQCN, org.apache.logging.log4j.Level.FATAL, message, t);
     }
 
-    public boolean isFatalEnabled() {
-        return logger.isFatalEnabled();
-    }
-
-    public void info(final Object message) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null);
-    }
-
-    public void info(final Object message, final Throwable t) {
-        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t);
-    }
-
-    public boolean isInfoEnabled() {
-        return logger.isInfoEnabled();
-    }
-
-    public boolean isEnabledFor(final Priority level) {
-        final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
-        return isEnabledFor(lvl);
-    }
-
     /**
-     * No-op implementation.
-     * @param appender The Appender to add.
-     */
-    @Override
-    public void addAppender(final Appender appender) {
-    }
-
-    /**
-     * No-op implementation.
-     * @param event The logging event.
-     */
-    public void callAppenders(final LoggingEvent event) {
-    }
-
-    /**
-     * Closes all attached appenders implementing the AppenderAttachable interface.
+     * LoggerRepository forgot the fireRemoveAppenderEvent method, if using the stock Hierarchy implementation, then call
+     * its fireRemove. Custom repositories can implement HierarchyEventListener if they want remove notifications.
      *
-     * @since 1.0
+     * @param appender appender, may be null.
      */
-    synchronized void closeNestedAppenders() {
-        final Enumeration enumeration = this.getAllAppenders();
-        if (enumeration != null) {
-            while (enumeration.hasMoreElements()) {
-                final Appender a = (Appender) enumeration.nextElement();
-                if (a instanceof AppenderAttachable) {
-                    a.close();
-                }
+    private void fireRemoveAppenderEvent(final Appender appender) {
+        if (appender != null) {
+            if (repository instanceof Hierarchy) {
+                ((Hierarchy) repository).fireRemoveAppenderEvent(this, appender);
+            } else if (repository instanceof HierarchyEventListener) {
+                ((HierarchyEventListener) repository).removeAppenderEvent(this, appender);
             }
         }
     }
 
-    @Override
-    @SuppressWarnings("rawtypes")
-    public Enumeration getAllAppenders() {
-        return NullEnumeration.getInstance();
-    }
-
-    /**
-     * No-op implementation.
-     * @param name The name of the Appender.
-     * @return null.
-     */
-    @Override
-    public Appender getAppender(final String name) {
-        return null;
+    public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) {
+        final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
+        if (logger instanceof ExtendedLogger) {
+            @SuppressWarnings("unchecked")
+            final Message msg = message instanceof Message ? (Message) message
+                : message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
+            ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t);
+        } else {
+            final ObjectRenderer renderer = get(message.getClass());
+            final Message msg = message instanceof Message ? (Message) message
+                : renderer != null ? new RenderedMessage(renderer, message) : new ObjectMessage(message);
+            logger.log(lvl, msg, t);
+        }
     }
 
-    /**
-     Is the appender passed as parameter attached to this category?
-     * @param appender The Appender to add.
-     * @return true if the appender is attached.
-     */
-    @Override
-    public boolean isAttached(final Appender appender) {
-        return false;
+    private <T> ObjectRenderer get(final Class<T> clazz) {
+        ObjectRenderer renderer = null;
+        for (Class<? super T> c = clazz; c != null; c = c.getSuperclass()) {
+            renderer = rendererMap.get(c);
+            if (renderer != null) {
+                return renderer;
+            }
+            renderer = searchInterfaces(c);
+            if (renderer != null) {
+                return renderer;
+            }
+        }
+        return null;
     }
 
-    /**
-     * No-op implementation.
-     */
-    @Override
-    public void removeAllAppenders() {
+    public boolean getAdditivity() {
+        return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false;
     }
 
     /**
-     * No-op implementation.
-     * @param appender The Appender to remove.
+     * Get the appenders contained in this category as an {@link Enumeration}. If no appenders can be found, then a
+     * {@link NullEnumeration} is returned.
+     *
+     * @return Enumeration An enumeration of the appenders in this category.
      */
     @Override
-    public void removeAppender(final Appender appender) {
+    @SuppressWarnings("rawtypes")
+    public Enumeration getAllAppenders() {
+        return aai == null ? NullEnumeration.getInstance() : aai.getAllAppenders();
     }
 
     /**
-     * No-op implementation.
-     * @param name The Appender to remove.
+     * Look for the appender named as <code>name</code>.
+     * <p>
+     * Return the appender with that name if in the list. Return <code>null</code> otherwise.
+     * </p>
      */
     @Override
-    public void removeAppender(final String name) {
+    public Appender getAppender(final String name) {
+        Appender appender = aai != null ? aai.getAppender(name) : null;
+        if (appender == null && LogManager.isLog4jCorePresent()) {
+            final org.apache.logging.log4j.core.Appender coreAppender = CategoryUtil.getAppenders(logger).get(name);
+            if (coreAppender != null) {
+                addAppender(appender = new AppenderWrapper(coreAppender));
+            }
+        }
+        return appender;
     }
 
-    /**
-     * No-op implementation.
-     */
-    public static void shutdown() {
+    public Priority getChainedPriority() {
+        return getEffectiveLevel();
     }
 
-    public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) {
-        final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
-        if (logger instanceof ExtendedLogger) {
-            @SuppressWarnings("unchecked")
-            final
-            Message msg = message instanceof Message ? (Message) message : message instanceof Map ?
-                    new MapMessage((Map) message) : new ObjectMessage(message);
-            ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t);
-        } else {
-            final ObjectRenderer renderer = get(message.getClass());
-            final Message msg = message instanceof Message ? (Message) message : renderer != null ?
-                    new RenderedMessage(renderer, message) : new ObjectMessage(message);
-            logger.log(lvl, msg, t);
+    public Level getEffectiveLevel() {
+        switch (logger.getLevel().getStandardLevel()) {
+        case ALL:
+            return Level.ALL;
+        case TRACE:
+            return Level.TRACE;
+        case DEBUG:
+            return Level.DEBUG;
+        case INFO:
+            return Level.INFO;
+        case WARN:
+            return Level.WARN;
+        case ERROR:
+            return Level.ERROR;
+        case FATAL:
+            return Level.FATAL;
+        default:
+            // TODO Should this be an IllegalStateException?
+            return Level.OFF;
         }
     }
 
     /**
-     * Tests if the named category exists (in the default hierarchy).
-     *
-     * @param name The name to test.
-     * @return Whether the name exists.
+     * Gets the the {@link LoggerRepository} where this <code>Category</code> instance is attached.
      *
-     * @deprecated Please use {@link LogManager#exists(String)} instead.
-     * @since 0.8.5
+     * @deprecated Please use {@link #getLoggerRepository()} instead.
+     * @since 1.1
      */
     @Deprecated
-    public static Logger exists(final String name) {
-        return LogManager.exists(name);
+    public LoggerRepository getHierarchy() {
+        return repository;
     }
 
-    public boolean getAdditivity() {
-        return LogManager.isLog4jCorePresent() ? CategoryUtil.isAdditive(logger) : false;
+    public final Level getLevel() {
+        return getEffectiveLevel();
     }
 
-    public void setAdditivity(final boolean additivity) {
-        if (LogManager.isLog4jCorePresent()) {
-            CategoryUtil.setAdditivity(logger, additivity);
-        }
+    private String getLevelStr(final Priority priority) {
+        return priority == null ? null : priority.levelStr;
+    }
+
+    org.apache.logging.log4j.Logger getLogger() {
+        return logger;
     }
 
     /**
-     * Only the Hiearchy class can set the hiearchy of a category. Default package access is MANDATORY here.
+     * Gets the the {@link LoggerRepository} where this <code>Category</code> is attached.
+     *
+     * @since 1.2
      */
-    final void setHierarchy(final LoggerRepository repository) {
-        this.repository = repository;
+    public LoggerRepository getLoggerRepository() {
+        return repository;
     }
 
-    public void setResourceBundle(final ResourceBundle bundle) {
-        this.bundle = bundle;
+    public final String getName() {
+        return logger.getName();
+    }
+
+    public final Category getParent() {
+        if (!LogManager.isLog4jCorePresent()) {
+            return null;
+        }
+        final org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger);
+        final LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger);
+        if (parent == null || loggerContext == null) {
+            return null;
+        }
+        final ConcurrentMap<String, Logger> loggers = Hierarchy.getLoggersMap(loggerContext);
+        final Logger parentLogger = loggers.get(parent.getName());
+        return parentLogger == null ? new Category(parent) : parentLogger;
+    }
+
+    public final Level getPriority() {
+        return getEffectiveLevel();
     }
 
     public ResourceBundle getResourceBundle() {
@@ -471,39 +474,51 @@ public class Category implements AppenderAttachable {
         return null;
     }
 
-    private static  String getSubName(final String name) {
-        if (Strings.isEmpty(name)) {
-            return null;
-        }
-        final int i = name.lastIndexOf('.');
-        return i > 0 ? name.substring(0, i) : Strings.EMPTY;
+    public void info(final Object message) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, null);
+    }
+
+    public void info(final Object message, final Throwable t) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.INFO, message, t);
     }
 
     /**
-     * If <code>assertion</code> parameter is {@code false}, then logs
-     * <code>msg</code> as an {@link #error(Object) error} statement.
+     * Is the appender passed as parameter attached to this category?
      *
-     * <p>
-     * The <code>assert</code> method has been renamed to <code>assertLog</code>
-     * because <code>assert</code> is a language reserved word in JDK 1.4.
-     * </p>
-     *
-     * @param assertion The assertion.
-     * @param msg       The message to print if <code>assertion</code> is false.
-     *
-     * @since 1.2
+     * @param appender The Appender to add.
+     * @return true if the appender is attached.
      */
-    public void assertLog(final boolean assertion, final String msg) {
-        if (!assertion) {
-            this.error(msg);
-        }
+    @Override
+    public boolean isAttached(final Appender appender) {
+        return aai == null ? false : aai.isAttached(appender);
     }
 
-    public void l7dlog(final Priority priority, final String key, final Throwable t) {
-        if (isEnabledFor(priority)) {
-            final Message msg = new LocalizedMessage(bundle, key, null);
-            forcedLog(FQCN, priority, msg, t);
-        }
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    private boolean isEnabledFor(final org.apache.logging.log4j.Level level) {
+        return logger.isEnabled(level);
+    }
+
+    public boolean isEnabledFor(final Priority level) {
+        return isEnabledFor(org.apache.logging.log4j.Level.toLevel(level.toString()));
+    }
+
+    public boolean isErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public boolean isFatalEnabled() {
+        return logger.isFatalEnabled();
+    }
+
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
     }
 
     public void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) {
@@ -513,10 +528,9 @@ public class Category implements AppenderAttachable {
         }
     }
 
-    public void log(final Priority priority, final Object message, final Throwable t) {
+    public void l7dlog(final Priority priority, final String key, final Throwable t) {
         if (isEnabledFor(priority)) {
-            @SuppressWarnings("unchecked")
-            final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
+            final Message msg = new LocalizedMessage(bundle, key, null);
             forcedLog(FQCN, priority, msg, t);
         }
     }
@@ -529,6 +543,14 @@ public class Category implements AppenderAttachable {
         }
     }
 
+    public void log(final Priority priority, final Object message, final Throwable t) {
+        if (isEnabledFor(priority)) {
+            @SuppressWarnings("unchecked")
+            final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
+            forcedLog(FQCN, priority, msg, t);
+        }
+    }
+
     public void log(final String fqcn, final Priority priority, final Object message, final Throwable t) {
         if (isEnabledFor(priority)) {
             final Message msg = new ObjectMessage(message);
@@ -536,18 +558,13 @@ public class Category implements AppenderAttachable {
         }
     }
 
-    void maybeLog(
-            final String fqcn,
-            final org.apache.logging.log4j.Level level,
-            final Object message,
-            final Throwable throwable) {
+    void maybeLog(final String fqcn, final org.apache.logging.log4j.Level level, final Object message, final Throwable throwable) {
         if (logger.isEnabled(level)) {
             final Message msg;
             if (message instanceof String) {
                 msg = new SimpleMessage((String) message);
             }
-            // SimpleMessage treats String and CharSequence differently, hence
-            // this else-if block.
+            // SimpleMessage treats String and CharSequence differently, hence this else-if block.
             else if (message instanceof CharSequence) {
                 msg = new SimpleMessage((CharSequence) message);
             } else if (message instanceof Map) {
@@ -565,23 +582,61 @@ public class Category implements AppenderAttachable {
         }
     }
 
-    private boolean isEnabledFor(final org.apache.logging.log4j.Level level) {
-        return logger.isEnabled(level);
-    }
-
-    private <T> ObjectRenderer get(final Class<T> clazz) {
-        ObjectRenderer renderer = null;
-        for (Class<? super T> c = clazz; c != null; c = c.getSuperclass()) {
-            renderer = rendererMap.get(c);
-            if (renderer != null) {
-                return renderer;
+    /**
+     * Removes all previously added appenders from this Category instance.
+     * <p>
+     * This is useful when re-reading configuration information.
+     * </p>
+     */
+    @Override
+    public void removeAllAppenders() {
+        if (aai != null) {
+            final Vector appenders = new Vector();
+            for (final Enumeration iter = aai.getAllAppenders(); iter != null && iter.hasMoreElements();) {
+                appenders.add(iter.nextElement());
             }
-            renderer = searchInterfaces(c);
-            if (renderer != null) {
-                return renderer;
+            aai.removeAllAppenders();
+            for (final Object appender : appenders) {
+                fireRemoveAppenderEvent((Appender) appender);
             }
+            aai = null;
+        }
+    }
+
+    /**
+     * Removes the appender passed as parameter form the list of appenders.
+     *
+     * @param appender The Appender to remove.
+     * @since 0.8.2
+     */
+    @Override
+    public void removeAppender(final Appender appender) {
+        if (appender == null || aai == null) {
+            return;
+        }
+        final boolean wasAttached = aai.isAttached(appender);
+        aai.removeAppender(appender);
+        if (wasAttached) {
+            fireRemoveAppenderEvent(appender);
+        }
+    }
+
+    /**
+     * Removes the appender with the name passed as parameter form the list of appenders.
+     *
+     * @param name The Appender to remove.
+     * @since 0.8.2
+     */
+    @Override
+    public void removeAppender(final String name) {
+        if (name == null || aai == null) {
+            return;
+        }
+        final Appender appender = aai.getAppender(name);
+        aai.removeAppender(name);
+        if (appender != null) {
+            fireRemoveAppenderEvent(appender);
         }
-        return null;
     }
 
     ObjectRenderer searchInterfaces(final Class<?> c) {
@@ -599,4 +654,43 @@ public class Category implements AppenderAttachable {
         return null;
     }
 
+    public void setAdditivity(final boolean additivity) {
+        if (LogManager.isLog4jCorePresent()) {
+            CategoryUtil.setAdditivity(logger, additivity);
+        }
+    }
+
+    /**
+     * Only the Hiearchy class can set the hiearchy of a category. Default package access is MANDATORY here.
+     */
+    final void setHierarchy(final LoggerRepository repository) {
+        this.repository = repository;
+    }
+
+    public void setLevel(final Level level) {
+        setLevel(getLevelStr(level));
+    }
+
+    private void setLevel(final String levelStr) {
+        if (LogManager.isLog4jCorePresent()) {
+            CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr));
+        }
+    }
+
+    public void setPriority(final Priority priority) {
+        setLevel(getLevelStr(priority));
+    }
+
+    public void setResourceBundle(final ResourceBundle bundle) {
+        this.bundle = bundle;
+    }
+
+    public void warn(final Object message) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, null);
+    }
+
+    public void warn(final Object message, final Throwable t) {
+        maybeLog(FQCN, org.apache.logging.log4j.Level.WARN, message, t);
+    }
+
 }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java
index a7a059a..8b2f1f9 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Hierarchy.java
@@ -16,7 +16,7 @@
  */
 
 // WARNING This class MUST not have references to the Category or
-// WARNING RootCategory classes in its static initiliazation neither
+// WARNING RootCategory classes in its static initialization neither
 // WARNING directly nor indirectly.
 
 package org.apache.log4j;
@@ -29,17 +29,19 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.legacy.core.ContextUtil;
 import org.apache.log4j.or.ObjectRenderer;
 import org.apache.log4j.or.RendererMap;
 import org.apache.log4j.spi.HierarchyEventListener;
 import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggerRepository;
 import org.apache.log4j.spi.RendererSupport;
 import org.apache.log4j.spi.ThrowableRenderer;
 import org.apache.log4j.spi.ThrowableRendererSupport;
 import org.apache.logging.log4j.core.appender.AsyncAppender;
 import org.apache.logging.log4j.spi.AbstractLoggerAdapter;
 import org.apache.logging.log4j.spi.LoggerContext;
-import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.StackLocatorUtil;
 
 /**
  * This class is specialized in retrieving loggers by name and also maintaining the logger hierarchy.
@@ -58,7 +60,7 @@ import org.apache.logging.log4j.util.Strings;
  * provision node.
  * </p>
  */
-public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableRendererSupport {
+public class Hierarchy implements LoggerRepository, RendererSupport, ThrowableRendererSupport {
 
     private static class PrivateLoggerAdapter extends AbstractLoggerAdapter<Logger> {
 
@@ -170,12 +172,14 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      *
      * <p>
      * You should <em>really</em> know what you are doing before invoking this method.
+     * </p>
      *
      * @since 0.9.0
      */
     public void clear() {
         // System.out.println("\n\nAbout to clear internal hash table.");
         ht.clear();
+        getLoggersMap(getContext()).clear();
     }
 
     @Override
@@ -197,8 +201,15 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public Logger exists(final String name) {
-        final LoggerContext ctx = getContext();
-        if (!ctx.hasLogger(name)) {
+        return exists(name, getContext());
+    }
+
+    Logger exists(final String name, final ClassLoader classLoader) {
+        return exists(name, getContext(classLoader));
+    }
+
+    Logger exists(final String name, final LoggerContext loggerContext) {
+        if (!loggerContext.hasLogger(name)) {
             return null;
         }
         return Logger.getLogger(name);
@@ -227,6 +238,10 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         }
     }
 
+    LoggerContext getContext(final ClassLoader classLoader) {
+        return LogManager.getContext(classLoader);
+    }
+
     /**
      * @deprecated Please use {@link #getCurrentLoggers} instead.
      */
@@ -241,22 +256,25 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      *
      * <p>
      * The root logger is <em>not</em> included in the returned {@link Enumeration}.
+     * </p>
      */
     @Override
     public Enumeration getCurrentLoggers() {
         // The accumlation in v is necessary because not all elements in
         // ht are Logger objects as there might be some ProvisionNodes
         // as well.
-        final Vector v = new Vector(ht.size());
+//        final Vector v = new Vector(ht.size());
+//
+//        final Enumeration elems = ht.elements();
+//        while (elems.hasMoreElements()) {
+//            final Object o = elems.nextElement();
+//            if (o instanceof Logger) {
+//                v.addElement(o);
+//            }
+//        }
+//        return v.elements();
 
-        final Enumeration elems = ht.elements();
-        while (elems.hasMoreElements()) {
-            final Object o = elems.nextElement();
-            if (o instanceof Logger) {
-                v.addElement(o);
-            }
-        }
-        return v.elements();
+        return LogManager.getCurrentLoggers(StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
@@ -265,6 +283,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      * <p>
      * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated and
      * then linked with its existing ancestors as well as children.
+     * </p>
      *
      * @param name The name of the logger to retrieve.
      *
@@ -274,9 +293,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         return getInstance(getContext(), name);
     }
 
-    @Override
-    public Logger getLogger(final String name, final ClassLoader classLoader) {
-        return getInstance(LogManager.getContext(classLoader), name);
+    Logger getLogger(final String name, final ClassLoader classLoader) {
+        return getInstance(getContext(classLoader), name);
     }
 
     /**
@@ -285,6 +303,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      * <p>
      * If a logger of that name already exists, then it will be returned. Otherwise, a new logger will be instantiated by
      * the <code>factory</code> parameter and linked with its existing ancestors as well as children.
+     * </p>
      *
      * @param name The name of the logger to retrieve.
      * @param factory The factory that will make the new logger instance.
@@ -295,9 +314,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         return getInstance(getContext(), name, factory);
     }
 
-    @Override
-    public Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) {
-        return getInstance(LogManager.getContext(classLoader), name, factory);
+    Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) {
+        return getInstance(getContext(classLoader), name, factory);
     }
 
     /**
@@ -318,6 +336,10 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
         return getInstance(getContext(), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME);
     }
 
+    Logger getRootLogger(final ClassLoader classLoader) {
+        return getInstance(getContext(classLoader), org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME);
+    }
+
     /**
      * Gets a {@link Level} representation of the <code>enable</code> state.
      *
@@ -361,6 +383,7 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      *
      * <p>
      * Existing categories are not removed. They are just reset.
+     * </p>
      *
      * <p>
      * This method should be used sparingly and with care as it will block all logging until it is completed.
@@ -370,6 +393,15 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public void resetConfiguration() {
+        resetConfiguration(getContext());
+    }
+
+    void resetConfiguration(final ClassLoader classLoader) {
+        resetConfiguration(getContext(classLoader));
+    }
+
+    void resetConfiguration(final LoggerContext loggerContext) {
+        getLoggersMap(loggerContext).clear();
 
         getRootLogger().setLevel(Level.DEBUG);
         root.setResourceBundle(null);
@@ -413,13 +445,13 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
     /**
      * Enable logging for logging requests with level <code>l</code> or higher. By default all levels are enabled.
      *
-     * @param l The minimum level for which logging requests are sent to their appenders.
+     * @param level The minimum level for which logging requests are sent to their appenders.
      */
     @Override
-    public void setThreshold(final Level l) {
-        if (l != null) {
-            thresholdInt = l.level;
-            threshold = l;
+    public void setThreshold(final Level level) {
+        if (level != null) {
+            thresholdInt = level.level;
+            threshold = level;
         }
     }
 
@@ -428,9 +460,9 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public void setThreshold(final String levelStr) {
-        final Level l = Level.toLevel(levelStr, null);
-        if (l != null) {
-            setThreshold(l);
+        final Level level = Level.toLevel(levelStr, null);
+        if (level != null) {
+            setThreshold(level);
         } else {
             LogLog.warn("Could not convert [" + levelStr + "] to Level.");
         }
@@ -440,8 +472,8 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      * {@inheritDoc}
      */
     @Override
-    public void setThrowableRenderer(final ThrowableRenderer renderer) {
-        throwableRenderer = renderer;
+    public void setThrowableRenderer(final ThrowableRenderer throwableRenderer) {
+        this.throwableRenderer = throwableRenderer;
     }
 
     /**
@@ -461,26 +493,37 @@ public class Hierarchy implements LoggerRepository2, RendererSupport, ThrowableR
      */
     @Override
     public void shutdown() {
-        final Logger root = getRootLogger();
-
-        // begin by closing nested appenders
-        root.closeNestedAppenders();
-
-        synchronized (ht) {
-            Enumeration cats = this.getCurrentLoggers();
-            while (cats.hasMoreElements()) {
-                final Logger c = (Logger) cats.nextElement();
-                c.closeNestedAppenders();
-            }
-
-            // then, remove all appenders
-            root.removeAllAppenders();
-            cats = this.getCurrentLoggers();
-            while (cats.hasMoreElements()) {
-                final Logger c = (Logger) cats.nextElement();
-                c.removeAllAppenders();
-            }
-        }
+      shutdown(getContext());
+  }
+
+    public void shutdown(final ClassLoader classLoader) {
+      shutdown(org.apache.logging.log4j.LogManager.getContext(classLoader, false));
+  }
+
+    void shutdown(final LoggerContext context) {
+//      final Logger root = getRootLogger();
+//      // begin by closing nested appenders
+//      root.closeNestedAppenders();
+//
+//      synchronized (ht) {
+//          Enumeration cats = this.getCurrentLoggers();
+//          while (cats.hasMoreElements()) {
+//              final Logger c = (Logger) cats.nextElement();
+//              c.closeNestedAppenders();
+//          }
+//
+//          // then, remove all appenders
+//          root.removeAllAppenders();
+//          cats = this.getCurrentLoggers();
+//          while (cats.hasMoreElements()) {
+//              final Logger c = (Logger) cats.nextElement();
+//              c.removeAllAppenders();
+//          }
+//      }
+        getLoggersMap(context).clear();
+          if (LogManager.isLog4jCorePresent()) {
+              ContextUtil.shutdown(context);
+          }
     }
 
     /**
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java
index 46bff05..b18c8f1 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java
@@ -69,20 +69,30 @@ public final class LogManager {
         LOG4J_CORE_PRESENT = checkLog4jCore();
         //
         // By default we use a DefaultRepositorySelector which always returns 'hierarchy'.
-        Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
+        final Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
         repositorySelector = new DefaultRepositorySelector(hierarchy);
     }
 
     private static boolean checkLog4jCore() {
         try {
             return Class.forName("org.apache.logging.log4j.core.LoggerContext") != null;
-        } catch (Exception ex) {
+        } catch (final Throwable ex) {
             return false;
         }
     }
 
+    /**
+     * Tests if a logger for the given name exists.
+     *
+     * @param name logger name to test.
+     * @return whether a logger for the given name exists.
+     */
     public static Logger exists(final String name) {
-        return getLoggerRepository().exists(name);
+        return exists(name, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static Logger exists(final String name, final ClassLoader classLoader) {
+        return getHierarchy().exists(name, classLoader);
     }
 
     /**
@@ -98,7 +108,7 @@ public final class LogManager {
 
     /**
      * Gets an enumeration of the current loggers.
-     * 
+     *
      * @return an enumeration of the current loggers.
      */
     @SuppressWarnings("rawtypes")
@@ -116,27 +126,42 @@ public final class LogManager {
         // @formatter:on
     }
 
+    static Hierarchy getHierarchy() {
+        final LoggerRepository loggerRepository = getLoggerRepository();
+        return loggerRepository instanceof Hierarchy ? (Hierarchy) loggerRepository : null;
+    }
+
+    /**
+     * Gets the logger for the given class.
+     */
     public static Logger getLogger(final Class<?> clazz) {
-        // Depth 2 gets the call site of this method.
-        return getLoggerRepository2().getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2));
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(clazz.getName(), StackLocatorUtil.getCallerClassLoader(2))
+            : getLoggerRepository().getLogger(clazz.getName());
     }
 
+    /**
+     * Gets the logger for the given name.
+     */
     public static Logger getLogger(final String name) {
-        // Depth 2 gets the call site of this method.
-        return getLoggerRepository2().getLogger(name, StackLocatorUtil.getCallerClassLoader(2));
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, StackLocatorUtil.getCallerClassLoader(2)) : getLoggerRepository().getLogger(name);
     }
 
     static Logger getLogger(final String name, final ClassLoader classLoader) {
-        return getLoggerRepository2().getLogger(name, classLoader);
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, classLoader) : getLoggerRepository().getLogger(name);
     }
 
     public static Logger getLogger(final String name, final LoggerFactory factory) {
-        // Depth 2 gets the call site of this method.
-        return getLoggerRepository2().getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2));
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, factory, StackLocatorUtil.getCallerClassLoader(2))
+            : getLoggerRepository().getLogger(name, factory);
     }
 
     static Logger getLogger(final String name, final LoggerFactory factory, final ClassLoader classLoader) {
-        return getLoggerRepository2().getLogger(name, factory, classLoader);
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getLogger(name, factory, classLoader) : getLoggerRepository().getLogger(name, factory);
     }
 
     public static LoggerRepository getLoggerRepository() {
@@ -146,48 +171,62 @@ public final class LogManager {
         return repositorySelector.getLoggerRepository();
     }
 
-    static LoggerRepository2 getLoggerRepository2() {
-        // TODO Hack
-        return (LoggerRepository2) getLoggerRepository();
+    /**
+     * Gets the root logger.
+     */
+    public static Logger getRootLogger() {
+        return getRootLogger(StackLocatorUtil.getCallerClassLoader(2));
     }
 
-    public static Logger getRootLogger() {
-        return getLoggerRepository2().getRootLogger();
+    static Logger getRootLogger(final ClassLoader classLoader) {
+        final Hierarchy hierarchy = getHierarchy();
+        return hierarchy != null ? hierarchy.getRootLogger(classLoader) : getLoggerRepository().getRootLogger();
     }
 
     static boolean isLog4jCorePresent() {
         return LOG4J_CORE_PRESENT;
     }
 
-    static void reconfigure() {
+    static void reconfigure(final ClassLoader classLoader) {
         if (isLog4jCorePresent()) {
-            ContextUtil.reconfigure(Hierarchy.getContext());
+            ContextUtil.reconfigure(LogManager.getContext(classLoader));
         }
     }
 
-    /**
-     * No-op implementation.
-     */
     public static void resetConfiguration() {
-        // noop
+        resetConfiguration(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static void resetConfiguration(final ClassLoader classLoader) {
+        final Hierarchy hierarchy = getHierarchy();
+        if (hierarchy != null) {
+            hierarchy.resetConfiguration(classLoader);
+        } else {
+            getLoggerRepository().resetConfiguration();
+        }
     }
 
-    /**
-     * No-op implementation.
-     * 
-     * @param selector The RepositorySelector.
-     * @param guard prevents calls at the incorrect time.
-     * @throws IllegalArgumentException if a parameter is invalid.
-     */
     public static void setRepositorySelector(final RepositorySelector selector, final Object guard) throws IllegalArgumentException {
-        // noop
+        if (selector == null) {
+            throw new IllegalArgumentException("RepositorySelector must be non-null.");
+        }
+        LogManager.repositorySelector = selector;
     }
 
     /**
-     * No-op implementation.
+     * Shuts down the current configuration.
      */
     public static void shutdown() {
-        // noop
+        shutdown(StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static void shutdown(final ClassLoader classLoader) {
+        final Hierarchy hierarchy = getHierarchy();
+        if (hierarchy != null) {
+            hierarchy.shutdown(classLoader);
+        } else {
+            getLoggerRepository().shutdown();
+        }
     }
 
 }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java
deleted file mode 100644
index 30c88ee..0000000
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/LoggerRepository2.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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;
-
-import org.apache.log4j.spi.LoggerFactory;
-import org.apache.log4j.spi.LoggerRepository;
-
-/**
- * A LoggerRepository that accounts for the caller's class loader.
- */
-interface LoggerRepository2 extends LoggerRepository {
-
-    Logger getLogger(String name, ClassLoader classLoader);
-
-    Logger getLogger(String name, LoggerFactory factory, ClassLoader classLoader);
-}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
index 808220a..45a96d4 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/PropertyConfigurator.java
@@ -16,114 +16,657 @@
  */
 package org.apache.log4j;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.InterruptedIOException;
 import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
 import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Vector;
 
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.config.PropertySetter;
+import org.apache.log4j.helpers.FileWatchdog;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.or.RendererMap;
 import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerFactory;
 import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.RendererSupport;
+import org.apache.log4j.spi.ThrowableRenderer;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.util.StackLocatorUtil;
 
 /**
- * A configurator for properties.
+ * Configures Log4j from properties.
  */
 public class PropertyConfigurator implements Configurator {
 
+    static class NameValue {
+        String key, value;
+
+        public NameValue(final String key, final String value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return key + "=" + value;
+        }
+    }
+
+    static class PropertyWatchdog extends FileWatchdog {
+
+        private final ClassLoader classLoader;
+
+        PropertyWatchdog(final String fileName, final ClassLoader classLoader) {
+            super(fileName);
+            this.classLoader = classLoader;
+        }
+
+        /**
+         * Call {@link PropertyConfigurator#configure(String)} with the <code>filename</code> to reconfigure log4j.
+         */
+        @Override
+        public void doOnChange() {
+            new PropertyConfigurator().doConfigure(filename, LogManager.getLoggerRepository(), classLoader);
+        }
+    }
+
+    class SortedKeyEnumeration implements Enumeration {
+
+        private final Enumeration e;
+
+        public SortedKeyEnumeration(final Hashtable ht) {
+            final Enumeration f = ht.keys();
+            final Vector keys = new Vector(ht.size());
+            for (int i, last = 0; f.hasMoreElements(); ++last) {
+                final String key = (String) f.nextElement();
+                for (i = 0; i < last; ++i) {
+                    final String s = (String) keys.get(i);
+                    if (key.compareTo(s) <= 0) {
+                        break;
+                    }
+                }
+                keys.add(i, key);
+            }
+            e = keys.elements();
+        }
+
+        @Override
+        public boolean hasMoreElements() {
+            return e.hasMoreElements();
+        }
+
+        @Override
+        public Object nextElement() {
+            return e.nextElement();
+        }
+    }
+
+    static final String CATEGORY_PREFIX = "log4j.category.";
+    static final String LOGGER_PREFIX = "log4j.logger.";
+    static final String FACTORY_PREFIX = "log4j.factory";
+    static final String ADDITIVITY_PREFIX = "log4j.additivity.";
+    static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
+    static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
+    static final String APPENDER_PREFIX = "log4j.appender.";
+    static final String RENDERER_PREFIX = "log4j.renderer.";
+    static final String THRESHOLD_PREFIX = "log4j.threshold";
+
+    private static final String THROWABLE_RENDERER_PREFIX = "log4j.throwableRenderer";
+    private static final String LOGGER_REF = "logger-ref";
+    private static final String ROOT_REF = "root-ref";
+    private static final String APPENDER_REF_TAG = "appender-ref";
+
+    /**
+     * Key for specifying the {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}. Currently set to
+     * "<code>log4j.loggerFactory</code>".
+     */
+    public static final String LOGGER_FACTORY_KEY = "log4j.loggerFactory";
+
+    /**
+     * If property set to true, then hierarchy will be reset before configuration.
+     */
+    private static final String RESET_KEY = "log4j.reset";
+
+    static final private String INTERNAL_ROOT_NAME = "root";
+
     /**
      * Reads configuration options from an InputStream.
      *
      * @param inputStream The input stream
      */
     public static void configure(final InputStream inputStream) {
+        new PropertyConfigurator().doConfigure(inputStream, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from <code>properties</code>.
+     * Reads configuration options from <code>properties</code>.
      *
      * See {@link #doConfigure(String, LoggerRepository)} for the expected format.
      *
      * @param properties The properties
      */
     public static void configure(final Properties properties) {
+        new PropertyConfigurator().doConfigure(properties, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from configuration file.
+     * Reads configuration options from configuration file.
      *
-     * @param configFileName The configuration file.
+     * @param fileName The configuration file.
      */
-    public static void configure(final String configFileName) {
+    public static void configure(final String fileName) {
+        new PropertyConfigurator().doConfigure(fileName, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from url <code>configURL</code>.
+     * Reads configuration options from url <code>configURL</code>.
      *
      * @param configURL The configuration URL
      */
     public static void configure(final URL configURL) {
+        new PropertyConfigurator().doConfigure(configURL, LogManager.getLoggerRepository(), StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Like {@link #configureAndWatch(String, long)} except that the
-     * default delay as defined by FileWatchdog.DEFAULT_DELAY is
-     * used.
+     * Like {@link #configureAndWatch(String, long)} except that the default delay as defined by FileWatchdog.DEFAULT_DELAY
+     * is used.
      *
      * @param configFilename A file in key=value format.
      */
     public static void configureAndWatch(final String configFilename) {
+        configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY, StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read the configuration file <code>configFilename</code> if it
-     * exists. Moreover, a thread will be created that will periodically
-     * check if <code>configFilename</code> has been created or
-     * modified. The period is determined by the <code>delay</code>
-     * argument. If a change or file creation is detected, then
-     * <code>configFilename</code> is read to configure log4j.
+     * Reads the configuration file <code>configFilename</code> if it exists. Moreover, a thread will be created that will
+     * periodically check if <code>configFilename</code> has been created or modified. The period is determined by the
+     * <code>delay</code> argument. If a change or file creation is detected, then <code>configFilename</code> is read to
+     * configure log4j.
      *
      * @param configFilename A file in key=value format.
-     * @param delay The delay in milliseconds to wait between each check.
+     * @param delayMillis The delay in milliseconds to wait between each check.
      */
-    public static void configureAndWatch(final String configFilename, final long delay) {
+    public static void configureAndWatch(final String configFilename, final long delayMillis) {
+        configureAndWatch(configFilename, delayMillis, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    static void configureAndWatch(final String configFilename, final long delay, final ClassLoader classLoader) {
+        final PropertyWatchdog watchdog = new PropertyWatchdog(configFilename, classLoader);
+        watchdog.setDelay(delay);
+        watchdog.start();
+    }
+
+    private static Configuration reconfigure(final Configuration configuration) {
+        org.apache.logging.log4j.core.config.Configurator.reconfigure(configuration);
+        return configuration;
     }
 
     /**
-     * Read configuration options from an InputStream.
+     * Used internally to keep track of configured appenders.
+     */
+    protected Hashtable registry = new Hashtable(11);
+
+    private LoggerRepository repository;
+
+    protected LoggerFactory loggerFactory = new DefaultCategoryFactory();
+
+    /**
+     * Checks the provided <code>Properties</code> object for a {@link org.apache.log4j.spi.LoggerFactory LoggerFactory}
+     * entry specified by {@link #LOGGER_FACTORY_KEY}. If such an entry exists, an attempt is made to create an instance
+     * using the default constructor. This instance is used for subsequent Category creations within this configurator.
+     *
+     * @see #parseCatsAndRenderers
+     */
+    protected void configureLoggerFactory(final Properties properties) {
+        final String factoryClassName = OptionConverter.findAndSubst(LOGGER_FACTORY_KEY, properties);
+        if (factoryClassName != null) {
+            LogLog.debug("Setting category factory to [" + factoryClassName + "].");
+            loggerFactory = (LoggerFactory) OptionConverter.instantiateByClassName(factoryClassName, LoggerFactory.class, loggerFactory);
+            PropertySetter.setProperties(loggerFactory, properties, FACTORY_PREFIX + ".");
+        }
+    }
+
+    void configureRootCategory(final Properties properties, final LoggerRepository loggerRepository) {
+        String effectiveFrefix = ROOT_LOGGER_PREFIX;
+        String value = OptionConverter.findAndSubst(ROOT_LOGGER_PREFIX, properties);
+
+        if (value == null) {
+            value = OptionConverter.findAndSubst(ROOT_CATEGORY_PREFIX, properties);
+            effectiveFrefix = ROOT_CATEGORY_PREFIX;
+        }
+
+        if (value == null) {
+            LogLog.debug("Could not find root logger information. Is this OK?");
+        } else {
+            final Logger root = loggerRepository.getRootLogger();
+            synchronized (root) {
+                parseCategory(properties, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
+            }
+        }
+    }
+
+    /**
+     * Reads configuration options from an InputStream.
      *
      * @param inputStream The input stream
-     * @param hierarchy The hierarchy
+     * @param loggerRepository The hierarchy
      */
     @Override
-    public void doConfigure(final InputStream inputStream, final LoggerRepository hierarchy) {
+    public void doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository) {
+        doConfigure(inputStream, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    Configuration doConfigure(final InputStream inputStream, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        return doConfigure(loadProperties(inputStream), loggerRepository, classLoader);
     }
 
     /**
-     * Read configuration options from <code>properties</code>.
+     * Reads configuration options from <code>properties</code>.
      *
      * See {@link #doConfigure(String, LoggerRepository)} for the expected format.
      *
      * @param properties The properties
-     * @param hierarchy The hierarchy
+     * @param loggerRepository The hierarchy
      */
-    public void doConfigure(final Properties properties, final LoggerRepository hierarchy) {
+    public void doConfigure(final Properties properties, final LoggerRepository loggerRepository) {
+        doConfigure(properties, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
     }
 
     /**
-     * Read configuration options from configuration file.
+     * Reads configuration options from <code>properties</code>.
+     *
+     * See {@link #doConfigure(String, LoggerRepository)} for the expected format.
      *
-     * @param configFileName The configuration file
-     * @param hierarchy The hierarchy
+     * @param properties The properties
+     * @param loggerRepository The hierarchy
      */
-    public void doConfigure(final String configFileName, final LoggerRepository hierarchy) {
+    Configuration doConfigure(final Properties properties, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        final PropertiesConfiguration configuration = new PropertiesConfiguration(LogManager.getContext(classLoader), properties);
+        configuration.doConfigure();
+
+        repository = loggerRepository;
+//      String value = properties.getProperty(LogLog.DEBUG_KEY);
+//      if (value == null) {
+//          value = properties.getProperty("log4j.configDebug");
+//          if (value != null) {
+//              LogLog.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
+//          }
+//      }
+//
+//      if (value != null) {
+//          LogLog.setInternalDebugging(OptionConverter.toBoolean(value, true));
+//      }
+//
+//      //
+//      // if log4j.reset=true then
+//      // reset hierarchy
+//      final String reset = properties.getProperty(RESET_KEY);
+//      if (reset != null && OptionConverter.toBoolean(reset, false)) {
+//          hierarchy.resetConfiguration();
+//      }
+//
+//      final String thresholdStr = OptionConverter.findAndSubst(THRESHOLD_PREFIX, properties);
+//      if (thresholdStr != null) {
+//          hierarchy.setThreshold(OptionConverter.toLevel(thresholdStr, (Level) Level.ALL));
+//          LogLog.debug("Hierarchy threshold set to [" + hierarchy.getThreshold() + "].");
+//      }
+//
+//      configureRootCategory(properties, hierarchy);
+//      configureLoggerFactory(properties);
+//      parseCatsAndRenderers(properties, hierarchy);
+//
+        // We don't want to hold references to appenders preventing their
+        // garbage collection.
+        registry.clear();
 
+        return reconfigure(configuration);
+    }
+
+    /**
+     * Reads configuration options from configuration file.
+     *
+     * @param fileName The configuration file
+     * @param loggerRepository The hierarchy
+     */
+    public void doConfigure(final String fileName, final LoggerRepository loggerRepository) {
+        doConfigure(fileName, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    /**
+     * Reads configuration options from configuration file.
+     *
+     * @param fileName The configuration file
+     * @param loggerRepository The hierarchy
+     */
+    Configuration doConfigure(final String fileName, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        try (InputStream inputStream = Files.newInputStream(Paths.get(fileName))) {
+            return doConfigure(inputStream, loggerRepository, classLoader);
+        } catch (final Exception e) {
+            if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+            LogLog.error("Could not read configuration file [" + fileName + "].", e);
+            LogLog.error("Ignoring configuration file [" + fileName + "].");
+            return null;
+        }
     }
 
     /**
      * Read configuration options from url <code>configURL</code>.
      *
-     * @param configURL The configuration URL
-     * @param hierarchy The hierarchy
+     * @param url The configuration URL
+     * @param loggerRepository The hierarchy
      */
     @Override
-    public void doConfigure(final URL configURL, final LoggerRepository hierarchy) {
+    public void doConfigure(final URL url, final LoggerRepository loggerRepository) {
+        doConfigure(url, loggerRepository, StackLocatorUtil.getCallerClassLoader(2));
+    }
+
+    Configuration doConfigure(final URL url, final LoggerRepository loggerRepository, final ClassLoader classLoader) {
+        LogLog.debug("Reading configuration from URL " + url);
+        try {
+            final URLConnection urlConnection = url.openConnection();
+            urlConnection.setUseCaches(false);
+            try (InputStream inputStream = urlConnection.getInputStream()) {
+                return doConfigure(inputStream, loggerRepository, classLoader);
+            }
+        } catch (final IOException e) {
+            LogLog.error("Could not read configuration file from URL [" + url + "].", e);
+            LogLog.error("Ignoring configuration file [" + url + "].");
+            return null;
+        }
+
+    }
+
+    private Properties loadProperties(final InputStream inputStream) {
+        final Properties loaded = new Properties();
+        try {
+            loaded.load(inputStream);
+        } catch (final IOException | IllegalArgumentException e) {
+            if (e instanceof InterruptedIOException) {
+                Thread.currentThread().interrupt();
+            }
+            LogLog.error("Could not read configuration file from InputStream [" + inputStream + "].", e);
+            LogLog.error("Ignoring configuration InputStream [" + inputStream + "].");
+            return null;
+        }
+        return loaded;
+    }
+
+    /**
+     * Parse the additivity option for a non-root category.
+     */
+    void parseAdditivityForLogger(final Properties properties, final Logger logger, final String loggerName) {
+        final String value = OptionConverter.findAndSubst(ADDITIVITY_PREFIX + loggerName, properties);
+        LogLog.debug("Handling " + ADDITIVITY_PREFIX + loggerName + "=[" + value + "]");
+        // touch additivity only if necessary
+        if ((value != null) && (!value.equals(""))) {
+            final boolean additivity = OptionConverter.toBoolean(value, true);
+            LogLog.debug("Setting additivity for \"" + loggerName + "\" to " + additivity);
+            logger.setAdditivity(additivity);
+        }
+    }
+
+    Appender parseAppender(final Properties properties, final String appenderName) {
+        Appender appender = registryGet(appenderName);
+        if ((appender != null)) {
+            LogLog.debug("Appender \"" + appenderName + "\" was already parsed.");
+            return appender;
+        }
+        // Appender was not previously initialized.
+        final String prefix = APPENDER_PREFIX + appenderName;
+        final String layoutPrefix = prefix + ".layout";
+
+        appender = (Appender) OptionConverter.instantiateByKey(properties, prefix, org.apache.log4j.Appender.class, null);
+        if (appender == null) {
+            LogLog.error("Could not instantiate appender named \"" + appenderName + "\".");
+            return null;
+        }
+        appender.setName(appenderName);
+
+        if (appender instanceof OptionHandler) {
+            if (appender.requiresLayout()) {
+                final Layout layout = (Layout) OptionConverter.instantiateByKey(properties, layoutPrefix, Layout.class, null);
+                if (layout != null) {
+                    appender.setLayout(layout);
+                    LogLog.debug("Parsing layout options for \"" + appenderName + "\".");
+                    // configureOptionHandler(layout, layoutPrefix + ".", props);
+                    PropertySetter.setProperties(layout, properties, layoutPrefix + ".");
+                    LogLog.debug("End of parsing for \"" + appenderName + "\".");
+                }
+            }
+            final String errorHandlerPrefix = prefix + ".errorhandler";
+            final String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, properties);
+            if (errorHandlerClass != null) {
+                final ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByKey(properties, errorHandlerPrefix, ErrorHandler.class, null);
+                if (eh != null) {
+                    appender.setErrorHandler(eh);
+                    LogLog.debug("Parsing errorhandler options for \"" + appenderName + "\".");
+                    parseErrorHandler(eh, errorHandlerPrefix, properties, repository);
+                    final Properties edited = new Properties();
+                    final String[] keys = new String[] {errorHandlerPrefix + "." + ROOT_REF, errorHandlerPrefix + "." + LOGGER_REF,
+                        errorHandlerPrefix + "." + APPENDER_REF_TAG};
+                    for (final Object element : properties.entrySet()) {
+                        final Map.Entry entry = (Map.Entry) element;
+                        int i = 0;
+                        for (; i < keys.length; i++) {
+                            if (keys[i].equals(entry.getKey())) {
+                                break;
+                            }
+                        }
+                        if (i == keys.length) {
+                            edited.put(entry.getKey(), entry.getValue());
+                        }
+                    }
+                    PropertySetter.setProperties(eh, edited, errorHandlerPrefix + ".");
+                    LogLog.debug("End of errorhandler parsing for \"" + appenderName + "\".");
+                }
+
+            }
+            // configureOptionHandler((OptionHandler) appender, prefix + ".", props);
+            PropertySetter.setProperties(appender, properties, prefix + ".");
+            LogLog.debug("Parsed \"" + appenderName + "\" options.");
+        }
+        parseAppenderFilters(properties, appenderName, appender);
+        registryPut(appender);
+        return appender;
+    }
+
+    void parseAppenderFilters(final Properties properties, final String appenderName, final Appender appender) {
+        // extract filters and filter options from props into a hashtable mapping
+        // the property name defining the filter class to a list of pre-parsed
+        // name-value pairs associated to that filter
+        final String filterPrefix = APPENDER_PREFIX + appenderName + ".filter.";
+        final int fIdx = filterPrefix.length();
+        final Hashtable filters = new Hashtable();
+        final Enumeration e = properties.keys();
+        String name = "";
+        while (e.hasMoreElements()) {
+            final String key = (String) e.nextElement();
+            if (key.startsWith(filterPrefix)) {
+                final int dotIdx = key.indexOf('.', fIdx);
+                String filterKey = key;
+                if (dotIdx != -1) {
+                    filterKey = key.substring(0, dotIdx);
+                    name = key.substring(dotIdx + 1);
+                }
+                Vector filterOpts = (Vector) filters.get(filterKey);
+                if (filterOpts == null) {
+                    filterOpts = new Vector();
+                    filters.put(filterKey, filterOpts);
+                }
+                if (dotIdx != -1) {
+                    final String value = OptionConverter.findAndSubst(key, properties);
+                    filterOpts.add(new NameValue(name, value));
+                }
+            }
+        }
+
+        // sort filters by IDs, insantiate filters, set filter options,
+        // add filters to the appender
+        final Enumeration g = new SortedKeyEnumeration(filters);
+        while (g.hasMoreElements()) {
+            final String key = (String) g.nextElement();
+            final String clazz = properties.getProperty(key);
+            if (clazz != null) {
+                LogLog.debug("Filter key: [" + key + "] class: [" + properties.getProperty(key) + "] props: " + filters.get(key));
+                final Filter filter = (Filter) OptionConverter.instantiateByClassName(clazz, Filter.class, null);
+                if (filter != null) {
+                    final PropertySetter propSetter = new PropertySetter(filter);
+                    final Vector v = (Vector) filters.get(key);
+                    final Enumeration filterProps = v.elements();
+                    while (filterProps.hasMoreElements()) {
+                        final NameValue kv = (NameValue) filterProps.nextElement();
+                        propSetter.setProperty(kv.key, kv.value);
+                    }
+                    propSetter.activate();
+                    LogLog.debug("Adding filter of type [" + filter.getClass() + "] to appender named [" + appender.getName() + "].");
+                    appender.addFilter(filter);
+                }
+            } else {
+                LogLog.warn("Missing class definition for filter: [" + key + "]");
+            }
+        }
+    }
+
+    /**
+     * This method must work for the root category as well.
+     */
+    void parseCategory(final Properties properties, final Logger logger, final String optionKey, final String loggerName, final String value) {
+
+        LogLog.debug("Parsing for [" + loggerName + "] with value=[" + value + "].");
+        // We must skip over ',' but not white space
+        final StringTokenizer st = new StringTokenizer(value, ",");
+
+        // If value is not in the form ", appender.." or "", then we should set
+        // the level of the loggeregory.
+
+        if (!(value.startsWith(",") || value.equals(""))) {
+
+            // just to be on the safe side...
+            if (!st.hasMoreTokens()) {
+                return;
+            }
+
+            final String levelStr = st.nextToken();
+            LogLog.debug("Level token is [" + levelStr + "].");
+
+            // If the level value is inherited, set category level value to
+            // null. We also check that the user has not specified inherited for the
+            // root category.
+            if (INHERITED.equalsIgnoreCase(levelStr) || NULL.equalsIgnoreCase(levelStr)) {
+                if (loggerName.equals(INTERNAL_ROOT_NAME)) {
+                    LogLog.warn("The root logger cannot be set to null.");
+                } else {
+                    logger.setLevel(null);
+                }
+            } else {
+                logger.setLevel(OptionConverter.toLevel(levelStr, (Level) Level.DEBUG));
+            }
+            LogLog.debug("Category " + loggerName + " set to " + logger.getLevel());
+        }
+
+        // Begin by removing all existing appenders.
+        logger.removeAllAppenders();
+
+        Appender appender;
+        String appenderName;
+        while (st.hasMoreTokens()) {
+            appenderName = st.nextToken().trim();
+            if (appenderName == null || appenderName.equals(",")) {
+                continue;
+            }
+            LogLog.debug("Parsing appender named \"" + appenderName + "\".");
+            appender = parseAppender(properties, appenderName);
+            if (appender != null) {
+                logger.addAppender(appender);
+            }
+        }
+    }
+
+    /**
+     * Parse non-root elements, such non-root categories and renderers.
+     */
+    protected void parseCatsAndRenderers(final Properties properties, final LoggerRepository loggerRepository) {
+        final Enumeration enumeration = properties.propertyNames();
+        while (enumeration.hasMoreElements()) {
+            final String key = (String) enumeration.nextElement();
+            if (key.startsWith(CATEGORY_PREFIX) || key.startsWith(LOGGER_PREFIX)) {
+                String loggerName = null;
+                if (key.startsWith(CATEGORY_PREFIX)) {
+                    loggerName = key.substring(CATEGORY_PREFIX.length());
+                } else if (key.startsWith(LOGGER_PREFIX)) {
+                    loggerName = key.substring(LOGGER_PREFIX.length());
+                }
+                final String value = OptionConverter.findAndSubst(key, properties);
+                final Logger logger = loggerRepository.getLogger(loggerName, loggerFactory);
+                synchronized (logger) {
+                    parseCategory(properties, logger, key, loggerName, value);
+                    parseAdditivityForLogger(properties, logger, loggerName);
+                }
+            } else if (key.startsWith(RENDERER_PREFIX)) {
+                final String renderedClass = key.substring(RENDERER_PREFIX.length());
+                final String renderingClass = OptionConverter.findAndSubst(key, properties);
+                if (loggerRepository instanceof RendererSupport) {
+                    RendererMap.addRenderer((RendererSupport) loggerRepository, renderedClass, renderingClass);
+                }
+            } else if (key.equals(THROWABLE_RENDERER_PREFIX)) {
+                if (loggerRepository instanceof ThrowableRendererSupport) {
+                    final ThrowableRenderer tr = (ThrowableRenderer) OptionConverter.instantiateByKey(properties, THROWABLE_RENDERER_PREFIX,
+                        org.apache.log4j.spi.ThrowableRenderer.class, null);
+                    if (tr == null) {
+                        LogLog.error("Could not instantiate throwableRenderer.");
+                    } else {
+                        final PropertySetter setter = new PropertySetter(tr);
+                        setter.setProperties(properties, THROWABLE_RENDERER_PREFIX + ".");
+                        ((ThrowableRendererSupport) loggerRepository).setThrowableRenderer(tr);
+
+                    }
+                }
+            }
+        }
+    }
+
+    private void parseErrorHandler(final ErrorHandler errorHandler, final String errorHandlerPrefix, final Properties props,
+        final LoggerRepository loggerRepository) {
+        final boolean rootRef = OptionConverter.toBoolean(OptionConverter.findAndSubst(errorHandlerPrefix + ROOT_REF, props), false);
+        if (rootRef) {
+            errorHandler.setLogger(loggerRepository.getRootLogger());
+        }
+        final String loggerName = OptionConverter.findAndSubst(errorHandlerPrefix + LOGGER_REF, props);
+        if (loggerName != null) {
+            final Logger logger = (loggerFactory == null) ? loggerRepository.getLogger(loggerName) : loggerRepository.getLogger(loggerName, loggerFactory);
+            errorHandler.setLogger(logger);
+        }
+        final String appenderName = OptionConverter.findAndSubst(errorHandlerPrefix + APPENDER_REF_TAG, props);
+        if (appenderName != null) {
+            final Appender backup = parseAppender(props, appenderName);
+            if (backup != null) {
+                errorHandler.setBackupAppender(backup);
+            }
+        }
+    }
+
+    Appender registryGet(final String name) {
+        return (Appender) registry.get(name);
+    }
+
+    void registryPut(final Appender appender) {
+        registry.put(appender.getName(), appender);
     }
 }
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
index 8be58d4..a8d8614 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
@@ -56,15 +56,21 @@ public class PropertiesConfiguration extends Log4j1Configuration {
     private static final String ROOT_CATEGORY_PREFIX = "log4j.rootCategory";
     private static final String ROOT_LOGGER_PREFIX = "log4j.rootLogger";
     private static final String APPENDER_PREFIX = "log4j.appender.";
-    private static final String LOGGER_REF	= "logger-ref";
-    private static final String ROOT_REF		= "root-ref";
+    private static final String LOGGER_REF = "logger-ref";
+    private static final String ROOT_REF = "root-ref";
     private static final String APPENDER_REF_TAG = "appender-ref";
-    public static final long DEFAULT_DELAY = 60000;
-    public static final String DEBUG_KEY="log4j.debug";
+
+    /**
+     * If property set to true, then hierarchy will be reset before configuration.
+     */
+    private static final String RESET_KEY = "log4j.reset";
+
+    public static final String DEBUG_KEY = "log4j.debug";
 
     private static final String INTERNAL_ROOT_NAME = "root";
 
     private final Map<String, Appender> registry = new HashMap<>();
+    private Properties properties;
 
     /**
      * Constructs a new instance.
@@ -73,20 +79,44 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      * @param source The ConfigurationSource.
      * @param monitorIntervalSeconds The monitoring interval in seconds.
      */
-    public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source,
-            final int monitorIntervalSeconds) {
+    public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source, final int monitorIntervalSeconds) {
         super(loggerContext, source, monitorIntervalSeconds);
     }
 
+    /**
+     * Constructs a new instance.
+     *
+     * @param loggerContext The LoggerContext.
+     * @param properties The ConfigurationSource, may be null.
+     */
+    public PropertiesConfiguration(final LoggerContext loggerContext, final Properties properties) {
+        super(loggerContext, ConfigurationSource.NULL_SOURCE, 0);
+        this.properties = properties;
+    }
+
+    /**
+     * Constructs a new instance.
+     *
+     * @param loggerContext The LoggerContext.
+     * @param properties The ConfigurationSource.
+     */
+    public PropertiesConfiguration(org.apache.logging.log4j.spi.LoggerContext loggerContext, Properties properties) {
+        this((LoggerContext) loggerContext, properties);
+    }
+
     @Override
     public void doConfigure() {
-        final InputStream inputStream = getConfigurationSource().getInputStream();
-        final Properties properties = new Properties();
-        try {
-            properties.load(inputStream);
-        } catch (final Exception e) {
-            LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e);
-            return;
+        if (properties == null) {
+            properties = new Properties();
+            final InputStream inputStream = getConfigurationSource().getInputStream();
+            if (inputStream != null) {
+                try {
+                    properties.load(inputStream);
+                } catch (final Exception e) {
+                    LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e);
+                    return;
+                }
+            }
         }
         // If we reach here, then the config file is alright.
         doConfigure(properties);
@@ -108,27 +138,26 @@ public class PropertiesConfiguration extends Log4j1Configuration {
     }
 
     /**
-     * Reads a configuration from a file. <b>The existing configuration is
-     * not cleared nor reset.</b> If you require a different behavior,
-     * then call {@link  LogManager#resetConfiguration
-     * resetConfiguration} method before calling
+     * Reads a configuration from a file. <b>The existing configuration is not cleared nor reset.</b> If you require a
+     * different behavior, then call {@link LogManager#resetConfiguration resetConfiguration} method before calling
      * <code>doConfigure</code>.
      *
-     * <p>The configuration file consists of statements in the format
-     * <code>key=value</code>. The syntax of different configuration
-     * elements are discussed below.
+     * <p>
+     * The configuration file consists of statements in the format <code>key=value</code>. The syntax of different
+     * configuration elements are discussed below.
      *
-     * <p>The level value can consist of the string values OFF, FATAL,
-     * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
-     * custom level value can be specified in the form
-     * level#classname. By default the repository-wide threshold is set
-     * to the lowest possible value, namely the level <code>ALL</code>.
+     * <p>
+     * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em>
+     * value. A custom level value can be specified in the form level#classname. By default the repository-wide threshold is
+     * set to the lowest possible value, namely the level <code>ALL</code>.
      * </p>
      *
      *
      * <h3>Appender configuration</h3>
      *
-     * <p>Appender configuration syntax is:
+     * <p>
+     * Appender configuration syntax is:
+     * 
      * <pre>
      * # For appender named <i>appenderName</i>, set its class.
      * # Note: The appender name can contain dots.
@@ -140,8 +169,8 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      * log4j.appender.appenderName.optionN=valueN
      * </pre>
      * <p>
-     * For each named appender you can configure its {@link Layout}. The
-     * syntax for configuring an appender's layout is:
+     * For each named appender you can configure its {@link Layout}. The syntax for configuring an appender's layout is:
+     * 
      * <pre>
      * log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
      * log4j.appender.appenderName.layout.option1=value1
@@ -150,18 +179,19 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      * </pre>
      * <p>
      * The syntax for adding {@link Filter}s to an appender is:
+     * 
      * <pre>
      * log4j.appender.appenderName.filter.ID=fully.qualified.name.of.filter.class
      * log4j.appender.appenderName.filter.ID.option1=value1
      * ...
      * log4j.appender.appenderName.filter.ID.optionN=valueN
      * </pre>
-     * The first line defines the class name of the filter identified by ID;
-     * subsequent lines with the same ID specify filter option - value
-     * pairs. Multiple filters are added to the appender in the lexicographic
-     * order of IDs.
+     * 
+     * The first line defines the class name of the filter identified by ID; subsequent lines with the same ID specify
+     * filter option - value pairs. Multiple filters are added to the appender in the lexicographic order of IDs.
      * <p>
      * The syntax for adding an {@link ErrorHandler} to an appender is:
+     * 
      * <pre>
      * log4j.appender.appenderName.errorhandler=fully.qualified.name.of.errorhandler.class
      * log4j.appender.appenderName.errorhandler.appender-ref=appenderName
@@ -172,111 +202,97 @@ public class PropertiesConfiguration extends Log4j1Configuration {
      *
      * <h3>Configuring loggers</h3>
      *
-     * <p>The syntax for configuring the root logger is:
+     * <p>
+     * The syntax for configuring the root logger is:
+     * 
      * <pre>
      * log4j.rootLogger=[level], appenderName, appenderName, ...
      * </pre>
      *
-     * <p>This syntax means that an optional <em>level</em> can be
-     * supplied followed by appender names separated by commas.
+     * <p>
+     * This syntax means that an optional <em>level</em> can be supplied followed by appender names separated by commas.
      *
-     * <p>The level value can consist of the string values OFF, FATAL,
-     * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
-     * custom level value can be specified in the form
-     * <code>level#classname</code>.
+     * <p>
+     * The level value can consist of the string values OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em>
+     * value. A custom level value can be specified in the form <code>level#classname</code>.
      *
-     * <p>If a level value is specified, then the root level is set
-     * to the corresponding level.  If no level value is specified,
+     * <p>
+     * If a level value is specified, then the root level is set to the corresponding level. If no level value is specified,
      * then the root level remains untouched.
      *
-     * <p>The root logger can be assigned multiple appenders.
+     * <p>
+     * The root logger can be assigned multiple appenders.
      *
-     * <p>Each <i>appenderName</i> (separated by commas) will be added to
-     * the root logger. The named appender is defined using the
-     * appender syntax defined above.
+     * <p>
+     * Each <i>appenderName</i> (separated by commas) will be added to the root logger. The named appender is defined using
+     * the appender syntax defined above.
      *
-     * <p>For non-root categories the syntax is almost the same:
+     * <p>
+     * For non-root categories the syntax is almost the same:
+     * 
      * <pre>
      * log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
      * </pre>
      *
-     * <p>The meaning of the optional level value is discussed above
-     * in relation to the root logger. In addition however, the value
-     * INHERITED can be specified meaning that the named logger should
-     * inherit its level from the logger hierarchy.
+     * <p>
+     * The meaning of the optional level value is discussed above in relation to the root logger. In addition however, the
+     * value INHERITED can be specified meaning that the named logger should inherit its level from the logger hierarchy.
      *
-     * <p>If no level value is supplied, then the level of the
-     * named logger remains untouched.
+     * <p>
+     * If no level value is supplied, then the level of the named logger remains untouched.
      *
-     * <p>By default categories inherit their level from the
-     * hierarchy. However, if you set the level of a logger and later
-     * decide that that logger should inherit its level, then you should
-     * specify INHERITED as the value for the level value. NULL is a
-     * synonym for INHERITED.
+     * <p>
+     * By default categories inherit their level from the hierarchy. However, if you set the level of a logger and later
+     * decide that that logger should inherit its level, then you should specify INHERITED as the value for the level value.
+     * NULL is a synonym for INHERITED.
      *
-     * <p>Similar to the root logger syntax, each <i>appenderName</i>
-     * (separated by commas) will be attached to the named logger.
+     * <p>
+     * Similar to the root logger syntax, each <i>appenderName</i> (separated by commas) will be attached to the named
+     * logger.
      *
-     * <p>See the <a href="../../../../manual.html#additivity">appender
-     * additivity rule</a> in the user manual for the meaning of the
-     * <code>additivity</code> flag.
+     * <p>
+     * See the <a href="../../../../manual.html#additivity">appender additivity rule</a> in the user manual for the meaning
+     * of the <code>additivity</code> flag.
      *
      *
-     * # Set options for appender named "A1".
-     * # Appender "A1" will be a SyslogAppender
+     * # Set options for appender named "A1". # Appender "A1" will be a SyslogAppender
      * log4j.appender.A1=org.apache.log4j.net.SyslogAppender
      *
-     * # The syslog daemon resides on www.abc.net
-     * log4j.appender.A1.SyslogHost=www.abc.net
-     *
-     * # A1's layout is a PatternLayout, using the conversion pattern
-     * # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log output will
-     * # include # the relative time since the start of the application in
-     * # milliseconds, followed by the level of the log request,
-     * # followed by the two rightmost components of the logger name,
-     * # followed by the callers method name, followed by the line number,
-     * # the nested diagnostic context and finally the message itself.
-     * # Refer to the documentation of {@link PatternLayout} for further information
-     * # on the syntax of the ConversionPattern key.
-     * log4j.appender.A1.layout=org.apache.log4j.PatternLayout
-     * log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2} %M.%L %x - %m\n
-     *
-     * # Set options for appender named "A2"
-     * # A2 should be a RollingFileAppender, with maximum file size of 10 MB
-     * # using at most one backup file. A2's layout is TTCC, using the
-     * # ISO8061 date format with context printing enabled.
-     * log4j.appender.A2=org.apache.log4j.RollingFileAppender
-     * log4j.appender.A2.MaxFileSize=10MB
-     * log4j.appender.A2.MaxBackupIndex=1
-     * log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
-     * log4j.appender.A2.layout.ContextPrinting=enabled
-     * log4j.appender.A2.layout.DateFormat=ISO8601
-     *
-     * # Root logger set to DEBUG using the A2 appender defined above.
-     * log4j.rootLogger=DEBUG, A2
-     *
-     * # Logger definitions:
-     * # The SECURITY logger inherits is level from root. However, it's output
-     * # will go to A1 appender defined above. It's additivity is non-cumulative.
-     * log4j.logger.SECURITY=INHERIT, A1
+     * # The syslog daemon resides on www.abc.net log4j.appender.A1.SyslogHost=www.abc.net
+     *
+     * # A1's layout is a PatternLayout, using the conversion pattern # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. Thus, the log
+     * output will # include # the relative time since the start of the application in # milliseconds, followed by the level
+     * of the log request, # followed by the two rightmost components of the logger name, # followed by the callers method
+     * name, followed by the line number, # the nested diagnostic context and finally the message itself. # Refer to the
+     * documentation of {@link PatternLayout} for further information # on the syntax of the ConversionPattern key.
+     * log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %c{2}
+     * %M.%L %x - %m\n
+     *
+     * # Set options for appender named "A2" # A2 should be a RollingFileAppender, with maximum file size of 10 MB # using
+     * at most one backup file. A2's layout is TTCC, using the # ISO8061 date format with context printing enabled.
+     * log4j.appender.A2=org.apache.log4j.RollingFileAppender log4j.appender.A2.MaxFileSize=10MB
+     * log4j.appender.A2.MaxBackupIndex=1 log4j.appender.A2.layout=org.apache.log4j.TTCCLayout
+     * log4j.appender.A2.layout.ContextPrinting=enabled log4j.appender.A2.layout.DateFormat=ISO8601
+     *
+     * # Root logger set to DEBUG using the A2 appender defined above. log4j.rootLogger=DEBUG, A2
+     *
+     * # Logger definitions: # The SECURITY logger inherits is level from root. However, it's output # will go to A1
+     * appender defined above. It's additivity is non-cumulative. log4j.logger.SECURITY=INHERIT, A1
      * log4j.additivity.SECURITY=false
      *
-     * # Only warnings or above will be logged for the logger "SECURITY.access".
-     * # Output will go to A1.
+     * # Only warnings or above will be logged for the logger "SECURITY.access". # Output will go to A1.
      * log4j.logger.SECURITY.access=WARN
      *
      *
-     * # The logger "class.of.the.day" inherits its level from the
-     * # logger hierarchy.  Output will go to the appender's of the root
-     * # logger, A2 in this case.
-     * log4j.logger.class.of.the.day=INHERIT
+     * # The logger "class.of.the.day" inherits its level from the # logger hierarchy. Output will go to the appender's of
+     * the root # logger, A2 in this case. log4j.logger.class.of.the.day=INHERIT
      * </pre>
      *
-     * <p>Refer to the <b>setOption</b> method in each Appender and
-     * Layout for class specific options.
+     * <p>
+     * Refer to the <b>setOption</b> method in each Appender and Layout for class specific options.
      *
-     * <p>Use the <code>#</code> or <code>!</code> characters at the
-     * beginning of a line for comments.
+     * <p>
+     * Use the <code>#</code> or <code>!</code> characters at the beginning of a line for comments.
      *
      * <p>
      */
@@ -297,6 +313,12 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(status);
         statusConfig.initialize();
 
+        // if log4j.reset=true then reset hierarchy
+        final String reset = properties.getProperty(RESET_KEY);
+        if (reset != null && OptionConverter.toBoolean(reset, false)) {
+            LogManager.resetConfiguration();
+        }
+
         configureRoot(properties);
         parseLoggers(properties);
 
@@ -368,7 +390,7 @@ public class PropertiesConfiguration extends Log4j1Configuration {
     /**
      * This method must work for the root category as well.
      */
-    private void parseLogger(final Properties props, final LoggerConfig logger, final String optionKey, final String loggerName, final String value) {
+    private void parseLogger(final Properties props, final LoggerConfig loggerConfig, final String optionKey, final String loggerName, final String value) {
 
         LOGGER.debug("Parsing for [{}] with value=[{}].", loggerName, value);
         // We must skip over ',' but not white space
@@ -386,9 +408,9 @@ public class PropertiesConfiguration extends Log4j1Configuration {
             final String levelStr = st.nextToken();
             LOGGER.debug("Level token is [{}].", levelStr);
 
-            final org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR :
-                    OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG);
-            logger.setLevel(level);
+            final org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR
+                : OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG);
+            loggerConfig.setLevel(level);
             LOGGER.debug("Logger {} level set to {}", loggerName, level);
         }
 
@@ -402,9 +424,8 @@ public class PropertiesConfiguration extends Log4j1Configuration {
             LOGGER.debug("Parsing appender named \"{}\".", appenderName);
             appender = parseAppender(props, appenderName);
             if (appender != null) {
-                LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName,
-                        logger.getName());
-                logger.addAppender(getAppender(appenderName), null, null);
+                LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", appenderName, loggerConfig.getName());
+                loggerConfig.addAppender(getAppender(appenderName), null, null);
             } else {
                 LOGGER.debug("Appender named [{}] not found.", appenderName);
             }
@@ -436,8 +457,8 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         return appender;
     }
 
-    private Appender buildAppender(final String appenderName, final String className, final String prefix,
-            final String layoutPrefix, final String filterPrefix, final Properties props) {
+    private Appender buildAppender(final String appenderName, final String className, final String prefix, final String layoutPrefix, final String filterPrefix,
+        final Properties props) {
         final Appender appender = newInstanceOf(className, "Appender");
         if (appender == null) {
             return null;
@@ -453,7 +474,7 @@ public class PropertiesConfiguration extends Log4j1Configuration {
             }
         }
         appender.addFilter(parseAppenderFilters(props, filterPrefix, appenderName));
-        final String[] keys = new String[] { layoutPrefix };
+        final String[] keys = new String[] {layoutPrefix};
         addProperties(appender, keys, props, prefix);
         if (appender instanceof AppenderWrapper) {
             addAppender(((AppenderWrapper) appender).getAppender());
@@ -487,14 +508,14 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         return layout;
     }
 
-    public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix,
-            final String errorHandlerClass, final Appender appender) {
+    public ErrorHandler parseErrorHandler(final Properties props, final String errorHandlerPrefix, final String errorHandlerClass, final Appender appender) {
         final ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler");
         final String[] keys = new String[] {
-                errorHandlerPrefix + "." + ROOT_REF,
-                errorHandlerPrefix + "." + LOGGER_REF,
-                errorHandlerPrefix + "." + APPENDER_REF_TAG
-        };
+            // @formatter:off
+            errorHandlerPrefix + "." + ROOT_REF, 
+            errorHandlerPrefix + "." + LOGGER_REF,
+            errorHandlerPrefix + "." + APPENDER_REF_TAG};
+            // @formatter:on
         addProperties(eh, keys, props, errorHandlerPrefix);
         return eh;
     }
@@ -515,7 +536,6 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         PropertySetter.setProperties(obj, edited, prefix + ".");
     }
 
-
     public Filter parseAppenderFilters(final Properties props, final String filterPrefix, final String appenderName) {
         // extract filters and filter options from props into a hashtable mapping
         // the property name defining the filter class to a list of pre-parsed
@@ -577,13 +597,11 @@ public class PropertiesConfiguration extends Log4j1Configuration {
         return filter;
     }
 
-
     private static <T> T newInstanceOf(final String className, final String type) {
         try {
             return LoaderUtil.newInstanceOf(className);
         } catch (ReflectiveOperationException ex) {
-            LOGGER.error("Unable to create {} {} due to {}:{}", type,  className,
-                    ex.getClass().getSimpleName(), ex.getMessage());
+            LOGGER.error("Unable to create {} {} due to {}:{}", type, className, ex.getClass().getSimpleName(), ex.getMessage());
             return null;
         }
     }
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java
new file mode 100644
index 0000000..d14dcf9
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/BasicConfiguratorTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import org.apache.log4j.varia.NullAppender;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test {@link BasicConfigurator}.
+ */
+public class BasicConfiguratorTest {
+
+    @Test
+    public void testConfigure() {
+        // TODO More...
+        BasicConfigurator.configure();
+    }
+
+    @Test
+    public void testResetConfiguration() {
+        // TODO More...
+        BasicConfigurator.resetConfiguration();
+    }
+
+    @Test
+    public void testConfigureAppender() {
+        BasicConfigurator.configure(null);
+        // TODO More...
+    }
+
+    @Test
+    public void testConfigureConsoleAppender() {
+        // TODO What to do? Map to Log4j 2 Appender deeper in the code?
+        BasicConfigurator.configure(new ConsoleAppender());
+    }
+
+    @Test
+    public void testConfigureNullAppender() {
+        // The NullAppender name is null and we do not want an NPE when the name is used as a key in a ConcurrentHashMap.
+        BasicConfigurator.configure(NullAppender.getNullAppender());
+    }
+}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java
new file mode 100644
index 0000000..26812ec
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/PropertyConfiguratorTest.java
@@ -0,0 +1,427 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Properties;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.spi.RootLogger;
+import org.apache.log4j.spi.ThrowableRenderer;
+import org.apache.log4j.spi.ThrowableRendererSupport;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link PropertyConfigurator}.
+ */
+public class PropertyConfiguratorTest {
+
+    /**
+     * Mock definition of FilterBasedTriggeringPolicy from extras companion.
+     */
+    public static final class FilterBasedTriggeringPolicy extends TriggeringPolicy {
+        private Filter filter;
+
+        public FilterBasedTriggeringPolicy() {
+        }
+
+        public Filter getFilter() {
+            return filter;
+
+        }
+
+        public void setFilter(final Filter val) {
+            filter = val;
+        }
+    }
+
+    /**
+     * Mock definition of FixedWindowRollingPolicy from extras companion.
+     */
+    public static final class FixedWindowRollingPolicy extends RollingPolicy {
+        private String activeFileName;
+        private String fileNamePattern;
+        private int minIndex;
+
+        public FixedWindowRollingPolicy() {
+            minIndex = -1;
+        }
+
+        public String getActiveFileName() {
+            return activeFileName;
+        }
+
+        public String getFileNamePattern() {
+            return fileNamePattern;
+        }
+
+        public int getMinIndex() {
+            return minIndex;
+        }
+
+        public void setActiveFileName(final String val) {
+            activeFileName = val;
+        }
+
+        public void setFileNamePattern(final String val) {
+            fileNamePattern = val;
+        }
+
+        public void setMinIndex(final int val) {
+            minIndex = val;
+        }
+    }
+
+    /**
+     * Mock ThrowableRenderer for testThrowableRenderer. See bug 45721.
+     */
+    public static class MockThrowableRenderer implements ThrowableRenderer, OptionHandler {
+        private boolean activated = false;
+        private boolean showVersion = true;
+
+        public MockThrowableRenderer() {
+        }
+
+        @Override
+        public void activateOptions() {
+            activated = true;
+        }
+
+        @Override
+        public String[] doRender(final Throwable t) {
+            return new String[0];
+        }
+
+        public boolean getShowVersion() {
+            return showVersion;
+        }
+
+        public boolean isActivated() {
+            return activated;
+        }
+
+        public void setShowVersion(final boolean v) {
+            showVersion = v;
+        }
+    }
+
+    /**
+     * Mock definition of org.apache.log4j.rolling.RollingFileAppender from extras companion.
+     */
+    public static final class RollingFileAppender extends AppenderSkeleton {
+        private RollingPolicy rollingPolicy;
+        private TriggeringPolicy triggeringPolicy;
+        private boolean append;
+
+        public RollingFileAppender() {
+
+        }
+
+        @Override
+        public void append(final LoggingEvent event) {
+        }
+
+        @Override
+        public void close() {
+        }
+
+        public boolean getAppend() {
+            return append;
+        }
+
+        public RollingPolicy getRollingPolicy() {
+            return rollingPolicy;
+        }
+
+        public TriggeringPolicy getTriggeringPolicy() {
+            return triggeringPolicy;
+        }
+
+        @Override
+        public boolean requiresLayout() {
+            return true;
+        }
+
+        public void setAppend(final boolean val) {
+            append = val;
+        }
+
+        public void setRollingPolicy(final RollingPolicy policy) {
+            rollingPolicy = policy;
+        }
+
+        public void setTriggeringPolicy(final TriggeringPolicy policy) {
+            triggeringPolicy = policy;
+        }
+    }
+
+    /**
+     * Mock definition of org.apache.log4j.rolling.RollingPolicy from extras companion.
+     */
+    public static class RollingPolicy implements OptionHandler {
+        private boolean activated = false;
+
+        public RollingPolicy() {
+
+        }
+
+        @Override
+        public void activateOptions() {
+            activated = true;
+        }
+
+        public final boolean isActivated() {
+            return activated;
+        }
+
+    }
+
+    /**
+     * Mock definition of TriggeringPolicy from extras companion.
+     */
+    public static class TriggeringPolicy implements OptionHandler {
+        private boolean activated = false;
+
+        public TriggeringPolicy() {
+
+        }
+
+        @Override
+        public void activateOptions() {
+            activated = true;
+        }
+
+        public final boolean isActivated() {
+            return activated;
+        }
+
+    }
+
+    private static final String FILTER1_PROPERTIES = "target/test-classes/log4j1-1.2.17/input/filter1.properties";
+
+    private static final String CAT_A_NAME = "categoryA";
+
+    private static final String CAT_B_NAME = "categoryB";
+
+    private static final String CAT_C_NAME = "categoryC";
+
+    /**
+     * Test for bug 40944. Did not catch IllegalArgumentException on Properties.load and close input stream.
+     *
+     * @throws IOException if IOException creating properties file.
+     */
+    @Test
+    public void testBadUnicodeEscape() throws IOException {
+        final String fileName = "target/badescape.properties";
+        try (FileWriter writer = new FileWriter(fileName)) {
+            writer.write("log4j.rootLogger=\\uXX41");
+        }
+        PropertyConfigurator.configure(fileName);
+        final File file = new File(fileName);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    /**
+     * Tests configuring Log4J from an InputStream.
+     *
+     * @since 1.2.17
+     */
+    @Test
+    public void testInputStream() throws IOException {
+        final Path file = Paths.get(FILTER1_PROPERTIES);
+        assertTrue(Files.exists(file));
+        try (InputStream inputStream = Files.newInputStream(file)) {
+            PropertyConfigurator.configure(inputStream);
+        }
+        this.validateNested();
+        LogManager.resetConfiguration();
+    }
+
+    /**
+     * Test for bug 47465. configure(URL) did not close opened JarURLConnection.
+     *
+     * @throws IOException if IOException creating properties jar.
+     */
+    @Test
+    public void testJarURL() throws IOException {
+        final File dir = new File("output");
+        dir.mkdirs();
+        final File file = new File("output/properties.jar");
+        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(file))) {
+            zos.putNextEntry(new ZipEntry(LogManager.DEFAULT_CONFIGURATION_FILE));
+            zos.write("log4j.rootLogger=debug".getBytes());
+            zos.closeEntry();
+        }
+        final URL url = new URL("jar:" + file.toURI().toURL() + "!/" + LogManager.DEFAULT_CONFIGURATION_FILE);
+        PropertyConfigurator.configure(url);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    @Test
+    public void testLocalVsGlobal() {
+        LoggerRepository repos1, repos2;
+        final Logger catA = Logger.getLogger(CAT_A_NAME);
+        final Logger catB = Logger.getLogger(CAT_B_NAME);
+        final Logger catC = Logger.getLogger(CAT_C_NAME);
+
+        final Properties globalSettings = new Properties();
+        globalSettings.put("log4j.logger." + CAT_A_NAME, Level.WARN.toString());
+        globalSettings.put("log4j.logger." + CAT_B_NAME, Level.WARN.toString());
+        globalSettings.put("log4j.logger." + CAT_C_NAME, Level.DEBUG.toString());
+        PropertyConfigurator.configure(globalSettings);
+        assertEquals(Level.WARN, catA.getLevel());
+        assertEquals(Level.WARN, catB.getLevel());
+        assertEquals(Level.DEBUG, catC.getLevel());
+
+        assertEquals(Level.WARN, catA.getLoggerRepository().getLogger(CAT_A_NAME).getLevel());
+        assertEquals(Level.WARN, catB.getLoggerRepository().getLogger(CAT_B_NAME).getLevel());
+        assertEquals(Level.DEBUG, catC.getLoggerRepository().getLogger(CAT_C_NAME).getLevel());
+
+        final Properties repos1Settings = new Properties();
+        repos1Settings.put("log4j.logger." + CAT_A_NAME, Level.DEBUG.toString());
+        repos1Settings.put("log4j.logger." + CAT_B_NAME, Level.INFO.toString());
+        repos1 = new Hierarchy(new RootLogger(Level.OFF));
+        new PropertyConfigurator().doConfigure(repos1Settings, repos1);
+        assertEquals(Level.DEBUG, repos1.getLogger(CAT_A_NAME).getLevel());
+        assertEquals(Level.INFO, repos1.getLogger(CAT_B_NAME).getLevel());
+
+        final Properties repos2Settings = new Properties();
+        repos2Settings.put("log4j.logger." + CAT_A_NAME, Level.INFO.toString());
+        repos2Settings.put("log4j.logger." + CAT_B_NAME, Level.DEBUG.toString());
+        repos2 = new Hierarchy(new RootLogger(Level.OFF));
+        new PropertyConfigurator().doConfigure(repos2Settings, repos2);
+        assertEquals(Level.INFO, repos2.getLogger(CAT_A_NAME).getLevel());
+        assertEquals(Level.DEBUG, repos2.getLogger(CAT_B_NAME).getLevel());
+    }
+
+    /**
+     * Tests processing of nested objects, see bug 36384.
+     */
+    public void testNested() {
+        try {
+            PropertyConfigurator.configure(FILTER1_PROPERTIES);
+            this.validateNested();
+        } finally {
+            LogManager.resetConfiguration();
+        }
+    }
+
+    /**
+     * Test processing of log4j.reset property, see bug 17531.
+     */
+    @Test
+    public void testReset() {
+        final VectorAppender appender = new VectorAppender();
+        appender.setName("A1");
+        Logger.getRootLogger().addAppender(appender);
+        final Properties properties = new Properties();
+        properties.put("log4j.reset", "true");
+        PropertyConfigurator.configure(properties);
+        assertNull(Logger.getRootLogger().getAppender("A1"));
+        LogManager.resetConfiguration();
+    }
+
+    /**
+     * Test of log4j.throwableRenderer support. See bug 45721.
+     */
+    public void testThrowableRenderer() {
+        final Properties properties = new Properties();
+        properties.put("log4j.throwableRenderer", "org.apache.log4j.PropertyConfiguratorTest$MockThrowableRenderer");
+        properties.put("log4j.throwableRenderer.showVersion", "false");
+        PropertyConfigurator.configure(properties);
+        final ThrowableRendererSupport repo = (ThrowableRendererSupport) LogManager.getLoggerRepository();
+        final MockThrowableRenderer renderer = (MockThrowableRenderer) repo.getThrowableRenderer();
+        LogManager.resetConfiguration();
+//        assertNotNull(renderer);
+//        assertEquals(true, renderer.isActivated());
+//        assertEquals(false, renderer.getShowVersion());
+    }
+
+    /**
+     * Test for bug 40944. configure(URL) never closed opened stream.
+     *
+     * @throws IOException if IOException creating properties file.
+     */
+    @Test
+    public void testURL() throws IOException {
+        final File file = new File("target/unclosed.properties");
+        try (FileWriter writer = new FileWriter(file)) {
+            writer.write("log4j.rootLogger=debug");
+        }
+        final URL url = file.toURI().toURL();
+        PropertyConfigurator.configure(url);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    /**
+     * Test for bug 40944. configure(URL) did not catch IllegalArgumentException and did not close stream.
+     *
+     * @throws IOException if IOException creating properties file.
+     */
+    @Test
+    public void testURLBadEscape() throws IOException {
+        final File file = new File("target/urlbadescape.properties");
+        try (FileWriter writer = new FileWriter(file)) {
+            writer.write("log4j.rootLogger=\\uXX41");
+        }
+        final URL url = file.toURI().toURL();
+        PropertyConfigurator.configure(url);
+        assertTrue(file.delete());
+        assertFalse(file.exists());
+    }
+
+    void validateNested() {
+        final Logger logger = Logger.getLogger("org.apache.log4j.PropertyConfiguratorTest");
+        final String appenderName = "ROLLING";
+        // Appender OK
+        final Appender appender = logger.getAppender(appenderName);
+        assertNotNull(appender);
+        // Down-cast?
+//        final RollingFileAppender rfa = (RollingFileAppender) appender;
+//        assertNotNull(appenderName, rfa);
+//        final FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) rfa.getRollingPolicy();
+//        assertEquals("filterBase-test1.log", rollingPolicy.getActiveFileName());
+//        assertEquals("filterBased-test1.%i", rollingPolicy.getFileNamePattern());
+//        assertEquals(0, rollingPolicy.getMinIndex());
+//        assertTrue(rollingPolicy.isActivated());
+//        final FilterBasedTriggeringPolicy triggeringPolicy = (FilterBasedTriggeringPolicy) rfa.getTriggeringPolicy();
+//        final LevelRangeFilter filter = (LevelRangeFilter) triggeringPolicy.getFilter();
+//        assertTrue(Level.INFO.equals(filter.getLevelMin()));
+    }
+}