Bug 42094: Add PluginConfigurator

git-svn-id: https://svn.apache.org/repos/asf/logging/sandbox/log4j/component@531106 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index 0f153f3..14129ca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
   <artifactId>apache-log4j-component</artifactId>
   <packaging>jar</packaging>
   <version>0.1-SNAPSHOT</version>
-  <name>Component</name>
+  <name>Apache Component Companion for log4j 1.2.</name>
   <description>This companion emulates the log4j 1.3 Component Framework to
   simplify the back-porting of code that made use of its services.  It does
   not provide any immediately useful functionality to log4j by itself,
@@ -150,10 +150,6 @@
           <groupId>org.codehaus.mojo</groupId>
           <artifactId>jxr-maven-plugin</artifactId>
       </plugin>
- 	<plugin> 
-    	<groupId>org.apache.maven.plugins</groupId> 
-    	<artifactId>maven-surefire-report-plugin</artifactId>    
-    </plugin> 
 	<plugin> 
  		<groupId>org.codehaus.mojo</groupId> 
  		<artifactId>cobertura-maven-plugin</artifactId>
diff --git a/src/main/java/org/apache/log4j/xml/PluginConfigurator.java b/src/main/java/org/apache/log4j/xml/PluginConfigurator.java
new file mode 100644
index 0000000..202f753
--- /dev/null
+++ b/src/main/java/org/apache/log4j/xml/PluginConfigurator.java
@@ -0,0 +1,258 @@
+/*
+ * 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.xml;
+
+import org.apache.log4j.LogManager;
+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.plugins.Plugin;
+import org.apache.log4j.spi.LoggerRepository;
+import org.apache.log4j.spi.LoggerRepositoryEx;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import javax.xml.parsers.FactoryConfigurationError;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class extends DOMConfigurator to support
+ * processing of plugin elements.
+ */
+public class PluginConfigurator extends
+        org.apache.log4j.xml.DOMConfigurator {
+
+    /**
+     * Create new instance.
+     */
+    public PluginConfigurator() {
+        super();
+    }
+
+    /**
+       Configure log4j using a <code>configuration</code> element as
+       defined in the log4j.dtd.
+
+    */
+    static
+    public
+    void configure (final Element element) {
+      PluginConfigurator configurator = new PluginConfigurator();
+      configurator.doConfigure(element,  LogManager.getLoggerRepository());
+    }
+
+    /**
+       A static version of {@link #doConfigure(String, LoggerRepository)}.  */
+    static
+    public
+    void configure(final String filename) throws FactoryConfigurationError {
+      new PluginConfigurator().doConfigure(filename, 
+                        LogManager.getLoggerRepository());
+    }
+
+    /**
+       A static version of {@link #doConfigure(URL, LoggerRepository)}.
+     */
+    static
+    public
+    void configure(final URL url) throws FactoryConfigurationError {
+      new PluginConfigurator().doConfigure(url, LogManager.getLoggerRepository());
+    }
+
+    /**
+       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.
+
+        @param configFilename A log4j configuration file in XML format.
+        @param delay The delay in milliseconds to wait between each check.
+    */
+    static
+    public
+    void configureAndWatch(final String configFilename, final long delay) {
+      PluginWatchdog xdog = new PluginWatchdog(configFilename);
+      xdog.setDelay(delay);
+      xdog.start();
+    }
+
+
+
+   /**
+       Like {@link #configureAndWatch(String, long)} except that the
+       default delay as defined by {@link FileWatchdog#DEFAULT_DELAY} is
+       used.
+
+       @param configFilename A log4j configuration file in XML format.
+
+    */
+    static
+    public
+    void configureAndWatch(final String configFilename) {
+      configureAndWatch(configFilename, FileWatchdog.DEFAULT_DELAY);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void parse(final Element element) {
+        super.parse(element);
+        if (repository instanceof LoggerRepositoryEx) {
+            List plugins = new ArrayList();
+            createPlugins((LoggerRepositoryEx) repository, plugins, element);
+            for (Iterator iter = plugins.iterator(); iter.hasNext();) {
+                ((Plugin) iter.next()).activateOptions();
+            }
+        }
+    }
+
+    /**
+     * Iterates over child nodes looking for plugin elements
+     * and constructs the plugins.
+     * @param repox repository
+     * @param plugins list to receive created plugins
+     * @param node parent node
+     */
+    private void createPlugins(
+            final LoggerRepositoryEx repox,
+            final List plugins, final Node node) {
+        Node child = node.getFirstChild();
+        while (child != null) {
+            switch (child.getNodeType()) {
+                case Node.ELEMENT_NODE:
+                    if ("plugin".equals(child.getNodeName())) {
+                        createPlugin(repox, plugins, (Element) child);
+                    }
+                    break;
+
+                case Node.ENTITY_REFERENCE_NODE:
+                    createPlugins(repox, plugins, child);
+                    break;
+
+                default:
+            }
+            child = child.getNextSibling();
+        }
+    }
+
+    /**
+     * Iterates over children of plugin node looking for
+     * param elements.
+     * @param propSetter property setter
+     * @param parent parent node
+     */
+    private void setParams(final PropertySetter propSetter,
+                           final Node parent) {
+        Node child = parent.getFirstChild();
+        while (child != null) {
+            switch (child.getNodeType()) {
+                case Node.ELEMENT_NODE:
+                    if ("param".equals(child.getNodeName())) {
+                        setParameter((Element) child, propSetter);
+                    }
+                    break;
+
+                case Node.ENTITY_REFERENCE_NODE:
+                    setParams(propSetter, child);
+                    break;
+
+                default:
+            }
+            child = child.getNextSibling();
+        }
+
+
+    }
+
+    /**
+     * Creates a plugin
+     * @param repox repository
+     * @param plugins list of receive created plugins
+     * @param element plugin element
+     */
+    private void createPlugin(
+            final LoggerRepositoryEx repox,
+            final List plugins,
+            final Element element) {
+        String className = element.getAttribute("class");
+
+        if (className.length() > 0)
+            try {
+                LogLog.debug(
+                        "About to instantiate plugin of type [" + className + "]");
+
+                Plugin plugin = (Plugin)
+                        OptionConverter.instantiateByClassName(
+                                className, org.apache.log4j.plugins.Plugin.class, null);
+
+                String pluginName = subst(element.getAttribute("name"));
+
+                if (pluginName.length() == 0) {
+                    LogLog.warn(
+                            "No plugin name given for plugin of type " + className + "].");
+                } else {
+                    plugin.setName(pluginName);
+                    LogLog.debug("plugin named as [" + pluginName + "]");
+                }
+
+                PropertySetter propSetter = new PropertySetter(plugin);
+                setParams(propSetter, element);
+
+                repox.getPluginRegistry().addPlugin(plugin);
+                plugin.setLoggerRepository(repox);
+
+                LogLog.debug("Pushing plugin on to the object stack.");
+                plugins.add(plugin);
+            } catch (Exception oops) {
+                LogLog.error(
+                        "Could not create a plugin. Reported error follows.", oops);
+            }
+    }
+
+    /**
+     * File change monitor class.
+     */
+    private static class PluginWatchdog extends FileWatchdog {
+
+        /**
+         * Construct new instance.
+         * @param filename
+         */
+      public PluginWatchdog(final String filename) {
+        super(filename);
+      }
+
+      /**
+         Call configure with the
+         <code>filename</code> to reconfigure log4j. */
+      public
+      void doOnChange() {
+        new PluginConfigurator().doConfigure(filename,
+                          LogManager.getLoggerRepository());
+      }
+    }
+
+
+}
diff --git a/src/test/java/org/apache/log4j/xml/MockReceiver.java b/src/test/java/org/apache/log4j/xml/MockReceiver.java
new file mode 120000
index 0000000..1138da8
--- /dev/null
+++ b/src/test/java/org/apache/log4j/xml/MockReceiver.java
@@ -0,0 +1 @@
+/Users/curta/ls-svn/log4j/tests/src/java/org/apache/log4j/plugins/MockReceiver.java
\ No newline at end of file
diff --git a/src/test/java/org/apache/log4j/xml/PluginConfiguratorTest.java b/src/test/java/org/apache/log4j/xml/PluginConfiguratorTest.java
new file mode 100644
index 0000000..4aacd1b
--- /dev/null
+++ b/src/test/java/org/apache/log4j/xml/PluginConfiguratorTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.xml;
+
+import junit.framework.TestCase;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.LoggerRepositoryExImpl;
+import org.apache.log4j.plugins.PluginRegistry;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+
+/**
+ * Tests for PluginConfigurator.
+ */
+public final class PluginConfiguratorTest extends TestCase {
+
+    /**
+     * Create new instance.
+     *
+     * @param testName test name.
+     */
+    public PluginConfiguratorTest(final String testName) {
+        super(testName);
+    }
+
+    /**
+     * Test parsing a file containing a plugin element.
+     */
+    public void testParse() throws IOException {
+        InputStream is =
+                PluginConfiguratorTest.class.getResourceAsStream("plugins1.xml");
+        if (is == null) {
+            throw new FileNotFoundException("Unable to find resource");
+        }
+        PluginConfigurator conf = new PluginConfigurator();
+        LoggerRepositoryExImpl repo = new LoggerRepositoryExImpl(
+                LogManager.getLoggerRepository());
+        conf.doConfigure(is, repo);
+        PluginRegistry plugins = repo.getPluginRegistry();
+        assertTrue(plugins.pluginNameExists("mock1"));
+        List receivers = plugins.getPlugins(
+                MockReceiver.class);
+        assertEquals(1, receivers.size());
+        MockReceiver mock1 = (MockReceiver) receivers.get(0);
+        assertEquals("mock1", mock1.getName());
+        assertEquals("127.0.0.1", mock1.getHost());
+        assertEquals(4560, mock1.getPort());
+        assertTrue(mock1.isActive());
+        plugins.stopAllPlugins();
+        assertFalse(mock1.isActive());
+    }
+}
diff --git a/src/test/resources/org/apache/log4j/xml/plugins1.xml b/src/test/resources/org/apache/log4j/xml/plugins1.xml
new file mode 100644
index 0000000..e148e4d
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/xml/plugins1.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  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.
+-->
+<!DOCTYPE log4j:configuration >
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true">
+   <plugin name="mock1" class="org.apache.log4j.xml.MockReceiver">
+      <param name="Port" value="4560"/>
+      <param name="Host" value="127.0.0.1" />
+   </plugin>
+   <root>
+      <level value="debug"/>
+   </root>
+</log4j:configuration>