Bug 41483: Add RewriteAppender and policies to replace MapFilter, etc

git-svn-id: https://svn.apache.org/repos/asf/logging/log4j/companions/receivers/trunk@564831 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index b693d5e..1cea147 100644
--- a/pom.xml
+++ b/pom.xml
@@ -216,7 +216,7 @@
     <dependency>
       <groupId>log4j</groupId>
       <artifactId>log4j</artifactId>
-      <version>1.2.15</version>
+      <version>1.2.15-SNAPSHOT</version>
     </dependency>    
     <dependency>
       <groupId>log4j</groupId>
diff --git a/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java b/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java
new file mode 100644
index 0000000..90040fb
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.java
@@ -0,0 +1,85 @@
+/*
+ * 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.rewrite;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.apache.log4j.rewrite.RewritePolicy;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * This policy rewrites events where the message of the
+ * original event implementes java.util.Map.
+ * All other events are passed through unmodified.
+ * If the map contains a "message" entry, the value will be
+ * used as the message for the rewritten event.  The rewritten
+ * event will have a property set that is the combination of the
+ * original property set and the other members of the message map.
+ * If both the original property set and the message map
+ * contain the same entry, the value from the message map
+ * will overwrite the original property set.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the MapFilter from log4j 1.3. 
+ */
+public class MapRewritePolicy implements RewritePolicy {
+    /**
+     * {@inheritDoc}
+     */
+    public LoggingEvent rewrite(final LoggingEvent source) {
+        Object msg = source.getMessage();
+        if (msg instanceof Map) {
+            Map props = new HashMap(source.getProperties());
+            Map eventProps = (Map) msg;
+            //
+            //   if the map sent in the logging request
+            //      has "message" entry, use that as the message body
+            //      otherwise, use the entire map.
+            //
+            Object newMsg = eventProps.get("message");
+            if (newMsg == null) {
+                newMsg = msg;
+            }
+
+            for(Iterator iter = eventProps.entrySet().iterator();
+                    iter.hasNext();
+                  ) {
+                Map.Entry entry = (Map.Entry) iter.next();
+                if (!("message".equals(entry.getKey()))) {
+                    props.put(entry.getKey(), entry.getValue());
+                }
+            }
+
+            return new LoggingEvent(
+                    source.getFQNOfLoggerClass(),
+                    source.getLogger(),
+                    source.getTimeStamp(),
+                    source.getLevel(),
+                    newMsg,
+                    source.getThreadName(),
+                    source.getThrowableInformation(),
+                    source.getNDC(),
+                    source.getLocationInformation(),
+                    props);
+        } else {
+            return source;
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java b/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java
new file mode 100644
index 0000000..a561a85
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java
@@ -0,0 +1,86 @@
+/*
+ * 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.rewrite;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.rewrite.RewritePolicy;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * This policy rewrites events by adding
+ * a user-specified list of properties to the event.
+ * Existing properties are not modified.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the PropertyFilter from log4j 1.3.
+ */
+
+public class PropertyRewritePolicy implements RewritePolicy {
+    private Map properties = Collections.EMPTY_MAP;
+    public PropertyRewritePolicy() {
+    }
+
+    public void setProperties(String props) {
+        Map hashTable = new HashMap();
+        StringTokenizer pairs = new StringTokenizer(props, ",");
+        while (pairs.hasMoreTokens()) {
+            StringTokenizer entry = new StringTokenizer(pairs.nextToken(), "=");
+            hashTable.put(entry.nextElement().toString().trim(), entry.nextElement().toString().trim());
+        }
+        synchronized(this) {
+            properties = hashTable;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public LoggingEvent rewrite(final LoggingEvent source) {
+        if (!properties.isEmpty()) {
+            Map rewriteProps = new HashMap(source.getProperties());
+            for(Iterator iter = properties.entrySet().iterator();
+                    iter.hasNext();
+                    ) {
+                Map.Entry entry = (Map.Entry) iter.next();
+                if (!rewriteProps.containsKey(entry.getKey())) {
+                    rewriteProps.put(entry.getKey(), entry.getValue());
+                }
+            }
+
+            return new LoggingEvent(
+                    source.getFQNOfLoggerClass(),
+                    source.getLogger(),
+                    source.getTimeStamp(),
+                    source.getLevel(),
+                    source.getMessage(),
+                    source.getThreadName(),
+                    source.getThrowableInformation(),
+                    source.getNDC(),
+                    source.getLocationInformation(),
+                    rewriteProps);
+        }
+        return source;
+    }
+
+
+
+}
diff --git a/src/main/java/org/apache/log4j/rewrite/ReflectionRewritePolicy.java b/src/main/java/org/apache/log4j/rewrite/ReflectionRewritePolicy.java
new file mode 100644
index 0000000..b27dfed
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rewrite/ReflectionRewritePolicy.java
@@ -0,0 +1,89 @@
+/*
+ * 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.rewrite;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.beans.PropertyDescriptor;
+import java.beans.Introspector;
+
+import org.apache.log4j.rewrite.RewritePolicy;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * This policy rewrites events by evaluating any
+ * JavaBean properties on the message object and adding them
+ * to the event properties.  If the message object has a
+ * message property, the value of that property will be
+ * used as the message for the rewritten event and will
+ * not be added to the event properties.  Values from the
+ * JavaBean properties will replace any existing property
+ * with the same name.
+ *
+ * The combination of the RewriteAppender and this policy
+ * performs the same actions as the ReflectionFilter from log4j 1.3. 
+ */
+public class ReflectionRewritePolicy implements RewritePolicy {
+    /**
+     * {@inheritDoc}
+     */
+    public LoggingEvent rewrite(final LoggingEvent source) {
+        Object msg = source.getMessage();
+        if (!(msg instanceof String)) {
+            Object newMsg = msg;
+            Map rewriteProps = new HashMap(source.getProperties());
+
+            try {
+                PropertyDescriptor[] props = Introspector.getBeanInfo(
+                        msg.getClass(), Object.class).getPropertyDescriptors();
+                if (props.length > 0) {
+                    for (int i=0;i<props.length;i++) {
+                        try {
+                            Object propertyValue =
+                                props[i].getReadMethod().invoke(msg,
+                                        (Object[]) null);
+                            if ("message".equalsIgnoreCase(props[i].getName())) {
+                                newMsg = propertyValue;
+                            } else {
+                                rewriteProps.put(props[i].getName(), propertyValue);
+                            }
+                        } catch (Exception e) {
+                            LogLog.warn("Unable to evaluate property " +
+                                    props[i].getName(), e);
+                        }
+                    }
+                    return new LoggingEvent(
+                            source.getFQNOfLoggerClass(),
+                            source.getLogger(),
+                            source.getTimeStamp(),
+                            source.getLevel(),
+                            newMsg,
+                            source.getThreadName(),
+                            source.getThrowableInformation(),
+                            source.getNDC(),
+                            source.getLocationInformation(),
+                            rewriteProps);
+                }
+            } catch (Exception e) {
+                LogLog.warn("Unable to get property descriptors", e);
+            }
+
+        }
+        return source;
+    }
+}
diff --git a/src/main/java/org/apache/log4j/rewrite/RewriteAppender.java b/src/main/java/org/apache/log4j/rewrite/RewriteAppender.java
new file mode 100644
index 0000000..368ecf9
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rewrite/RewriteAppender.java
@@ -0,0 +1,199 @@
+/*
+ * 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.rewrite;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.AppenderAttachableImpl;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.log4j.xml.UnrecognizedElementHandler;
+import org.w3c.dom.Element;
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * This appender forwards a logging request to another
+ * appender after possibly rewriting the logging event.
+ *
+ * This appender (with the appropriate policy)
+ * replaces the MapFilter, PropertyFilter and ReflectionFilter
+ * from log4j 1.3.
+ */
+public class RewriteAppender extends AppenderSkeleton
+     implements AppenderAttachable, UnrecognizedElementHandler {
+    /**
+     * Rewrite policy.
+     */
+    private RewritePolicy policy;
+    /**
+     * Nested appenders.
+     */
+    private final AppenderAttachableImpl appenders;
+
+    public RewriteAppender() {
+        appenders = new AppenderAttachableImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void append(final LoggingEvent event) {
+        LoggingEvent rewritten = event;
+        if (policy != null) {
+            rewritten = policy.rewrite(event);
+        }
+        if (rewritten != null) {
+            synchronized (appenders) {
+              appenders.appendLoopOnAppenders(rewritten);
+            }
+        }
+    }
+
+    /**
+     * Add appender.
+     *
+     * @param newAppender appender to add, may not be null.
+     */
+    public void addAppender(final Appender newAppender) {
+      synchronized (appenders) {
+        appenders.addAppender(newAppender);
+      }
+    }
+
+    /**
+     * Get iterator over attached appenders.
+     * @return iterator or null if no attached appenders.
+     */
+    public Enumeration getAllAppenders() {
+      synchronized (appenders) {
+        return appenders.getAllAppenders();
+      }
+    }
+
+    /**
+     * Get appender by name.
+     *
+     * @param name name, may not be null.
+     * @return matching appender or null.
+     */
+    public Appender getAppender(final String name) {
+      synchronized (appenders) {
+        return appenders.getAppender(name);
+      }
+    }
+
+
+    /**
+     * Close this <code>AsyncAppender</code> by interrupting the dispatcher
+     * thread which will process all pending events before exiting.
+     */
+    public void close() {
+      closed = true;
+      //
+      //    close all attached appenders.
+      //
+      synchronized (appenders) {
+        Enumeration iter = appenders.getAllAppenders();
+
+        if (iter != null) {
+          while (iter.hasMoreElements()) {
+            Object next = iter.nextElement();
+
+            if (next instanceof Appender) {
+              ((Appender) next).close();
+            }
+          }
+        }
+      }
+    }
+
+    /**
+     * Determines if specified appender is attached.
+     * @param appender appender.
+     * @return true if attached.
+     */
+    public boolean isAttached(final Appender appender) {
+      synchronized (appenders) {
+        return appenders.isAttached(appender);
+      }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean requiresLayout() {
+      return false;
+    }
+
+    /**
+     * Removes and closes all attached appenders.
+     */
+    public void removeAllAppenders() {
+      synchronized (appenders) {
+        appenders.removeAllAppenders();
+      }
+    }
+
+    /**
+     * Removes an appender.
+     * @param appender appender to remove.
+     */
+    public void removeAppender(final Appender appender) {
+      synchronized (appenders) {
+        appenders.removeAppender(appender);
+      }
+    }
+
+    /**
+     * Remove appender by name.
+     * @param name name.
+     */
+    public void removeAppender(final String name) {
+      synchronized (appenders) {
+        appenders.removeAppender(name);
+      }
+    }
+
+
+    public void setRewritePolicy(final RewritePolicy rewritePolicy) {
+        policy = rewritePolicy;
+    }
+    /**
+     * {@inheritDoc}
+     */
+    public boolean parseUnrecognizedElement(final Element element,
+                                            final Properties props) throws Exception {
+        final String nodeName = element.getNodeName();
+        if ("rewritePolicy".equals(nodeName)) {
+            Object rewritePolicy =
+                    org.apache.log4j.xml.DOMConfigurator.parseElement(
+                            element, props, RewritePolicy.class);
+            if (rewritePolicy != null) {
+                if (rewritePolicy instanceof OptionHandler) {
+                    ((OptionHandler) rewritePolicy).activateOptions();
+                }
+                this.setRewritePolicy((RewritePolicy) rewritePolicy);
+            }
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java b/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java
new file mode 100644
index 0000000..bb40507
--- /dev/null
+++ b/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java
@@ -0,0 +1,37 @@
+package org.apache.log4j.rewrite;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+/**
+ * This interface is implemented to provide a rewrite
+ * strategy for RewriteAppender.  RewriteAppender will
+ * call the rewrite method with a source logging event.
+ * The strategy may return that event, create a new event
+ * or return null to suppress the logging request.
+ */
+public interface RewritePolicy {
+    /**
+     * Rewrite a logging event.
+     * @param source a logging event that may be returned or
+     * used to create a new logging event.
+     * @return a logging event or null to suppress processing.
+     */
+    LoggingEvent rewrite(final LoggingEvent source);
+}
diff --git a/src/test/java/org/apache/log4j/rewrite/RewriteAppenderTest.java b/src/test/java/org/apache/log4j/rewrite/RewriteAppenderTest.java
new file mode 100644
index 0000000..4e131cc
--- /dev/null
+++ b/src/test/java/org/apache/log4j/rewrite/RewriteAppenderTest.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.rewrite;
+
+import junit.framework.*;
+import org.apache.log4j.*;
+import org.apache.log4j.util.Compare;
+import org.apache.log4j.xml.*;
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Hashtable;
+import javax.xml.parsers.*;
+import org.w3c.dom.*;
+
+public class RewriteAppenderTest extends TestCase {
+    public RewriteAppenderTest(final String name) {
+        super(name);
+    }
+
+    public void setUp() {
+        LogManager.getLoggerRepository().resetConfiguration();
+        Hashtable context = MDC.getContext();
+        if (context != null) {
+            context.clear();
+        }
+    }
+
+    public void tearDown() {
+        LogManager.getLoggerRepository().shutdown();
+    }
+
+    public void configure(final String resourceName) throws Exception {
+        InputStream is = RewriteAppenderTest.class.getResourceAsStream(resourceName);
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(false);
+        DocumentBuilder builder = factory.newDocumentBuilder();
+        Document doc = builder.parse(is);
+        DOMConfigurator.configure(doc.getDocumentElement());
+    }
+
+
+    public void testMapPolicy() throws Exception {
+        configure("map.xml");
+        Logger logger = Logger.getLogger(RewriteAppenderTest.class);
+        logger.info("Message 0");
+        MDC.put("p1", "Hola");
+
+        Map msg = new HashMap();
+        msg.put("p1", "Hello");
+        msg.put("p2", "World");
+        msg.put("x1", "Mundo");
+        logger.info(msg);
+        msg.put("message", "Message 1");
+        logger.info(msg);
+        assertTrue(Compare.compare(RewriteAppenderTest.class, "temp", "map.log"));
+    }
+
+    private static class BaseBean {
+        private final Object p2;
+        private final Object x1;
+
+        public BaseBean(final Object p2,
+                        final Object x1) {
+             this.p2 = p2;
+             this.x1 = x1;
+        }
+
+        public Object getP2() {
+            return p2;
+        }
+
+        public Object getX1() {
+            return x1;
+        }
+
+        public String toString() {
+            return "I am bean.";
+        }
+    }
+
+    private static class MessageBean extends BaseBean {
+        private final Object msg;
+
+        public MessageBean(final Object msg,
+                           final Object p2,
+                           final Object x1) {
+            super(p2, x1);
+            this.msg = msg;
+        }
+
+        public Object getMessage() {
+            return msg;
+        }
+    }
+
+    public void testReflectionPolicy() throws Exception {
+        configure("reflection.xml");
+        Logger logger = Logger.getLogger(RewriteAppenderTest.class);
+        logger.info("Message 0");
+        logger.info(new BaseBean("Hello", "World" ));
+        MDC.put("p1", "Hola");
+        MDC.put("p2", "p2");
+        logger.info(new MessageBean("Welcome to The Hub", "Hello", "World" ));
+        assertTrue(Compare.compare(RewriteAppenderTest.class, "temp", "reflection.log"));
+    }
+
+    public void testPropertyPolicy() throws Exception {
+        configure("property.xml");
+        Logger logger = Logger.getLogger(RewriteAppenderTest.class);
+        logger.info("Message 0");
+        MDC.put("p1", "Hola");
+        logger.info("Message 1");
+        assertTrue(Compare.compare(RewriteAppenderTest.class, "temp", "property.log"));
+    }
+}
diff --git a/src/test/java/org/apache/log4j/util/Compare.java b/src/test/java/org/apache/log4j/util/Compare.java
new file mode 100644
index 0000000..42aa233
--- /dev/null
+++ b/src/test/java/org/apache/log4j/util/Compare.java
@@ -0,0 +1,202 @@
+/*
+ * 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.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.zip.GZIPInputStream;
+
+
+public class Compare {
+  static final int B1_NULL = -1;
+  static final int B2_NULL = -2;
+
+  private static final InputStream open(
+          final Class testClass,
+          final String fileName) throws IOException {
+      String resourceName = fileName;
+      if (fileName.startsWith("witness/")) {
+          resourceName = fileName.substring(fileName.lastIndexOf('/') + 1);
+      }
+      InputStream is = testClass.getResourceAsStream(resourceName);
+      if (is == null) {
+          File file = new File(fileName);
+          if (file.exists()) {
+              is = new FileInputStream(file);
+          } else {
+              throw new FileNotFoundException("Resource "
+                      + resourceName + " not found");
+          }
+      }
+      return is;
+  }
+
+  public static boolean compare(Class testClass,
+                                final String file1,
+                                final String file2)
+    throws IOException {
+    BufferedReader in1 = new BufferedReader(new FileReader(file1));
+    BufferedReader in2 = new BufferedReader(new InputStreamReader(
+            open(testClass, file2)));
+    try {
+      return compare(testClass, file1, file2, in1, in2);
+    } finally {
+      in1.close();
+      in2.close();
+    }
+  }
+    
+ public static boolean compare(
+         Class testClass, String file1, String file2, BufferedReader in1, BufferedReader in2) throws IOException {
+
+    String s1;
+    int lineCounter = 0;
+
+    while ((s1 = in1.readLine()) != null) {
+      lineCounter++;
+
+      String s2 = in2.readLine();
+
+      if (!s1.equals(s2)) {
+        System.out.println(
+          "Files [" + file1 + "] and [" + file2 + "] differ on line "
+          + lineCounter);
+        System.out.println("One reads:  [" + s1 + "].");
+        System.out.println("Other reads:[" + s2 + "].");
+        outputFile(testClass, file1);
+        outputFile(testClass, file2);
+
+        return false;
+      }
+    }
+
+    // the second file is longer
+    if (in2.read() != -1) {
+      System.out.println(
+        "File [" + file2 + "] longer than file [" + file1 + "].");
+      outputFile(testClass, file1);
+      outputFile(testClass, file2);
+
+      return false;
+    }
+
+    return true;
+  }
+
+  /** 
+   * 
+   * Prints file on the console.
+   *
+   */
+  private static void outputFile(Class testClass, String file)
+    throws IOException {
+    InputStream is = open(testClass, file);
+    BufferedReader in1 = new BufferedReader(new InputStreamReader(is));
+
+    String s1;
+    int lineCounter = 0;
+    System.out.println("--------------------------------");
+    System.out.println("Contents of " + file + ":");
+
+    while ((s1 = in1.readLine()) != null) {
+      lineCounter++;
+      System.out.print(lineCounter);
+
+      if (lineCounter < 10) {
+        System.out.print("   : ");
+      } else if (lineCounter < 100) {
+        System.out.print("  : ");
+      } else if (lineCounter < 1000) {
+        System.out.print(" : ");
+      } else {
+        System.out.print(": ");
+      }
+
+      System.out.println(s1);
+    }
+    in1.close();
+  }
+
+
+    public static boolean gzCompare(final Class testClass,
+                                    final String actual,
+                                    final String expected)
+      throws FileNotFoundException, IOException {
+      String resourceName = expected;
+      int lastSlash = expected.lastIndexOf("/");
+      if (lastSlash >= 0) {
+          resourceName = expected.substring(lastSlash + 1);
+      }
+      InputStream resourceStream = testClass.getResourceAsStream(resourceName);
+      if (resourceStream == null) {
+          throw new FileNotFoundException("Could not locate resource " + resourceName);
+      }
+
+      BufferedReader in1 = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(actual))));
+      BufferedReader in2 = new BufferedReader(new InputStreamReader(new GZIPInputStream(resourceStream)));
+      try {
+        return gzCompare(testClass, actual, expected, in1, in2);
+      } finally {
+        in1.close();
+        in2.close();
+      }
+    }
+
+    public static boolean gzCompare(Class testClass, String file1, String file2, BufferedReader in1, BufferedReader in2) throws IOException {
+
+      String s1;
+      int lineCounter = 0;
+
+      while ((s1 = in1.readLine()) != null) {
+        lineCounter++;
+
+        String s2 = in2.readLine();
+
+        if (!s1.equals(s2)) {
+          System.out.println(
+            "Files [" + file1 + "] and [" + file2 + "] differ on line "
+            + lineCounter);
+          System.out.println("One reads:  [" + s1 + "].");
+          System.out.println("Other reads:[" + s2 + "].");
+          outputFile(testClass, file1);
+          outputFile(testClass, file2);
+
+          return false;
+        }
+      }
+
+      // the second file is longer
+      if (in2.read() != -1) {
+        System.out.println(
+          "File [" + file2 + "] longer than file [" + file1 + "].");
+        outputFile(testClass, file1);
+        outputFile(testClass, file2);
+
+        return false;
+      }
+
+      return true;
+    }
+
+}
diff --git a/src/test/resources/org/apache/log4j/rewrite/map.log b/src/test/resources/org/apache/log4j/rewrite/map.log
new file mode 100644
index 0000000..88407fd
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/rewrite/map.log
@@ -0,0 +1,3 @@
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2: Message 0
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World {p1=Hello, x1=Mundo, p2=World}
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World Message 1
diff --git a/src/test/resources/org/apache/log4j/rewrite/map.xml b/src/test/resources/org/apache/log4j/rewrite/map.xml
new file mode 100644
index 0000000..7cb60b7
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/rewrite/map.xml
@@ -0,0 +1,38 @@
+<!--
+ 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.
+
+-->
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
+    <appender name="F1" class="org.apache.log4j.FileAppender">
+        <param name="file"   value="temp"/>
+        <param name="append" value="false"/>
+        <layout class="org.apache.log4j.PatternLayout">
+           <param name="ConversionPattern" value="%p %c - p1:%X{p1} p2:%X{p2} %m%n"/>
+        </layout>
+    </appender>
+
+
+  <appender name="A1" class="org.apache.log4j.rewrite.RewriteAppender">
+      <appender-ref ref="F1"/>
+      <rewritePolicy class="org.apache.log4j.rewrite.MapRewritePolicy"/>
+  </appender>
+
+  <root>
+    <level value ="debug" />
+    <appender-ref ref="A1" />
+  </root>
+
+</log4j:configuration>
diff --git a/src/test/resources/org/apache/log4j/rewrite/property.log b/src/test/resources/org/apache/log4j/rewrite/property.log
new file mode 100644
index 0000000..9aa2c49
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/rewrite/property.log
@@ -0,0 +1,2 @@
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hello p2:World Message 0
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hola p2:World Message 1
diff --git a/src/test/resources/org/apache/log4j/rewrite/property.xml b/src/test/resources/org/apache/log4j/rewrite/property.xml
new file mode 100644
index 0000000..13a04f8
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/rewrite/property.xml
@@ -0,0 +1,40 @@
+<!--
+ 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.
+
+-->
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
+    <appender name="F1" class="org.apache.log4j.FileAppender">
+        <param name="file"   value="temp"/>
+        <param name="append" value="false"/>
+        <layout class="org.apache.log4j.PatternLayout">
+           <param name="ConversionPattern" value="%p %c - p1:%X{p1} p2:%X{p2} %m%n"/>
+        </layout>
+    </appender>
+
+
+  <appender name="A1" class="org.apache.log4j.rewrite.RewriteAppender">
+      <appender-ref ref="F1"/>
+      <rewritePolicy class="org.apache.log4j.rewrite.PropertyRewritePolicy">
+          <param name="properties" value="p1=Hello,p2=World,x1=3.1415"/>
+      </rewritePolicy>
+  </appender>
+
+  <root>
+    <level value ="debug" />
+    <appender-ref ref="A1" />
+  </root>
+
+</log4j:configuration>
diff --git a/src/test/resources/org/apache/log4j/rewrite/reflection.log b/src/test/resources/org/apache/log4j/rewrite/reflection.log
new file mode 100644
index 0000000..da0b52f
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/rewrite/reflection.log
@@ -0,0 +1,3 @@
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2: Message 0
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1: p2:Hello I am bean.
+INFO org.apache.log4j.rewrite.RewriteAppenderTest - p1:Hola p2:Hello Welcome to The Hub
diff --git a/src/test/resources/org/apache/log4j/rewrite/reflection.xml b/src/test/resources/org/apache/log4j/rewrite/reflection.xml
new file mode 100644
index 0000000..643850b
--- /dev/null
+++ b/src/test/resources/org/apache/log4j/rewrite/reflection.xml
@@ -0,0 +1,38 @@
+<!--
+ 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.
+
+-->
+<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
+    <appender name="F1" class="org.apache.log4j.FileAppender">
+        <param name="file"   value="temp"/>
+        <param name="append" value="false"/>
+        <layout class="org.apache.log4j.PatternLayout">
+           <param name="ConversionPattern" value="%p %c - p1:%X{p1} p2:%X{p2} %m%n"/>
+        </layout>
+    </appender>
+
+
+  <appender name="A1" class="org.apache.log4j.rewrite.RewriteAppender">
+      <appender-ref ref="F1"/>
+      <rewritePolicy class="org.apache.log4j.rewrite.ReflectionRewritePolicy"/>
+  </appender>
+
+  <root>
+    <level value ="debug" />
+    <appender-ref ref="A1" />
+  </root>
+
+</log4j:configuration>