    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.log4j.xml;
    19
    20  import org.apache.log4j.Layout;
    21  import org.apache.log4j.LayoutTest;
    22  import org.apache.log4j.Level;
    23  import org.apache.log4j.Logger;
    24  import org.apache.log4j.NDC;
    25  import org.apache.log4j.MDC;
    26  import org.apache.log4j.spi.LoggingEvent;
    27
    28  import org.w3c.dom.Document;
    29  import org.w3c.dom.Element;
    30  import org.w3c.dom.Node;
    31  import org.w3c.dom.NodeList;
    32
    33  import org.xml.sax.InputSource;
    34
    35  import java.io.Reader;
    36  import java.io.StringReader;
    37  import java.util.Hashtable;
    38
    39  import javax.xml.parsers.DocumentBuilder;
    40  import javax.xml.parsers.DocumentBuilderFactory;
    41
    42
    43  /**
    44   * Test for XMLLayout.
    45   *
    46   * @author Curt Arnold
    47   */
    48  public class XMLLayoutTest extends LayoutTest {
    49    /**
    50     * Construct new instance of XMLLayoutTest.
    51     *
    52     * @param testName test name.
    53     */
    54    public XMLLayoutTest(final String testName) {
    55      super(testName, "text/plain", false, null, null);
    56    }
    57
    58      /**
    59       * Clear MDC and NDC before test.
    60       */
    61    public void setUp() {
    62        NDC.clear();
    63        if (MDC.getContext() != null) {
    64          MDC.getContext().clear();
    65        }
    66    }
    67
    68      /**
    69       * Clear MDC and NDC after test.
    70       */
    71    public void tearDown() {
    72        setUp();
    73    }
    74
    75    /**
    76     * @{inheritDoc}
    77     */
    78    protected Layout createLayout() {
    79      return new XMLLayout();
    80    }
    81
    82    /**
    83     * Parses the string as the body of an XML document and returns the document element.
    84     * @param source source string.
    85     * @return document element.
    86     * @throws Exception if parser can not be constructed or source is not a valid XML document.
    87     */
    88    private Element parse(final String source) throws Exception {
    89      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    90      factory.setNamespaceAware(false);
    91      factory.setCoalescing(true);
    92
    93      DocumentBuilder builder = factory.newDocumentBuilder();
    94      Reader reader = new StringReader(source);
    95      Document doc = builder.parse(new InputSource(reader));
    96
    97      return doc.getDocumentElement();
    98    }
    99
    100   /**
    101    * Checks a log4j:event element against expectations.
    102    * @param element element, may not be null.
    103    * @param event event, may not be null.
    104    */
    105   private void checkEventElement(
    106     final Element element, final LoggingEvent event) {
    107     assertEquals("log4j:event", element.getTagName());
    108     assertEquals(
    109       event.getLoggerName(), element.getAttribute("logger"));
    110     assertEquals(
    111       Long.toString(event.timeStamp), element.getAttribute("timestamp"));
    112     assertEquals(event.getLevel().toString(), element.getAttribute("level"));
    113     assertEquals(event.getThreadName(), element.getAttribute("thread"));
    114   }
    115
    116   /**
    117    * Checks a log4j:message element against expectations.
    118    * @param element element, may not be null.
    119    * @param message expected message.
    120    */
    121   private void checkMessageElement(
    122     final Element element, final String message) {
    123     assertEquals("log4j:message", element.getTagName());
    124
    125     Node messageNode = element.getFirstChild();
    126     assertNotNull(messageNode);
    127     assertEquals(Node.TEXT_NODE, messageNode.getNodeType());
    128     assertEquals(message, messageNode.getNodeValue());
    129     assertNull(messageNode.getNextSibling());
    130   }
    131
    132   /**
    133    * Checks a log4j:message element against expectations.
    134    * @param element element, may not be null.
    135    * @param message expected message.
    136    */
    137   private void checkNDCElement(final Element element, final String message) {
    138     assertEquals("log4j:NDC", element.getTagName());
    139
    140     Node messageNode = element.getFirstChild();
    141     assertNotNull(messageNode);
    142     assertEquals(Node.TEXT_NODE, messageNode.getNodeType());
    143     assertEquals(message, messageNode.getNodeValue());
    144     assertNull(messageNode.getNextSibling());
    145   }
    146
    147   /**
    148    * Checks a log4j:throwable element against expectations.
    149    * @param element element, may not be null.
    150    * @param ex exception, may not be null.
    151    */
    152   private void checkThrowableElement(
    153     final Element element, final Exception ex) {
    154     assertEquals("log4j:throwable", element.getTagName());
    155
    156     Node messageNode = element.getFirstChild();
    157     assertNotNull(messageNode);
    158     assertEquals(Node.TEXT_NODE, messageNode.getNodeType());
    159
    160     String msg = ex.toString();
    161     assertEquals(msg, messageNode.getNodeValue().substring(0, msg.length()));
    162     assertNull(messageNode.getNextSibling());
    163   }
    164
    165     /**
    166      * Checks a log4j:properties element against expectations.
    167      * @param element element, may not be null.
    168      * @param key key.
    169      * @param value value.
    170      */
    171     private void checkPropertiesElement(
    172       final Element element, final String key, final String value) {
    173       assertEquals("log4j:properties", element.getTagName());
    174
    175       int childNodeCount = 0;
    176       for(Node child = element.getFirstChild();
    177                child != null;
    178                child = child.getNextSibling()) {
    179           if (child.getNodeType() == Node.ELEMENT_NODE) {
    180               assertEquals("log4j:data", child.getNodeName());
    181               Element childElement = (Element) child;
    182               assertEquals(key, childElement.getAttribute("name"));
    183               assertEquals(value, childElement.getAttribute("value"));
    184               childNodeCount++;
    185           }
    186       }
    187       assertEquals(1, childNodeCount);
    188     }
    189
    190   /**
    191    * Tests formatted results.
    192    * @throws Exception if parser can not be constructed or source is not a valid XML document.
    193    */
    194   public void testFormat() throws Exception {
    195     Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest");
    196     LoggingEvent event =
    197       new LoggingEvent(
    198         "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null);
    199     XMLLayout layout = (XMLLayout) createLayout();
    200     String result = layout.format(event);
    201     Element parsedResult = parse(result);
    202     checkEventElement(parsedResult, event);
    203
    204     int childElementCount = 0;
    205
    206     for (
    207       Node node = parsedResult.getFirstChild(); node != null;
    208         node = node.getNextSibling()) {
    209       switch (node.getNodeType()) {
    210       case Node.ELEMENT_NODE:
    211         childElementCount++;
    212         checkMessageElement((Element) node, "Hello, World");
    213
    214         break;
    215
    216       case Node.COMMENT_NODE:
    217         break;
    218
    219       case Node.TEXT_NODE:
    220
    221         //  should only be whitespace
    222         break;
    223
    224       default:
    225         fail("Unexpected node type");
    226
    227         break;
    228       }
    229     }
    230
    231     assertEquals(1, childElementCount);
    232   }
    233
    234   /**
    235    * Tests formatted results with an exception.
    236    * @throws Exception if parser can not be constructed or source is not a valid XML document.
    237    */
    238   public void testFormatWithException() throws Exception {
    239     Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest");
    240     Exception ex = new IllegalArgumentException("'foo' is not a valid name");
    241     LoggingEvent event =
    242       new LoggingEvent(
    243         "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", ex);
    244     XMLLayout layout = (XMLLayout) createLayout();
    245     String result = layout.format(event);
    246     Element parsedResult = parse(result);
    247     checkEventElement(parsedResult, event);
    248
    249     int childElementCount = 0;
    250
    251     for (
    252       Node node = parsedResult.getFirstChild(); node != null;
    253         node = node.getNextSibling()) {
    254       switch (node.getNodeType()) {
    255       case Node.ELEMENT_NODE:
    256         childElementCount++;
    257
    258         if (childElementCount == 1) {
    259           checkMessageElement((Element) node, "Hello, World");
    260         } else {
    261           checkThrowableElement((Element) node, ex);
    262         }
    263
    264         break;
    265
    266       case Node.COMMENT_NODE:
    267         break;
    268
    269       case Node.TEXT_NODE:
    270
    271         //  should only be whitespace
    272         break;
    273
    274       default:
    275         fail("Unexpected node type");
    276
    277         break;
    278       }
    279     }
    280
    281     assertEquals(2, childElementCount);
    282   }
    283
    284   /**
    285    * Tests formatted results with an exception.
    286    * @throws Exception if parser can not be constructed or source is not a valid XML document.
    287    */
    288   public void testFormatWithNDC() throws Exception {
    289     Logger logger = Logger.getLogger("org.apache.log4j.xml.XMLLayoutTest");
    290     NDC.push("NDC goes here");
    291
    292     LoggingEvent event =
    293       new LoggingEvent(
    294         "org.apache.log4j.Logger", logger, Level.INFO, "Hello, World", null);
    295     XMLLayout layout = (XMLLayout) createLayout();
    296     String result = layout.format(event);
    297     NDC.pop();
    298
    299     Element parsedResult = parse(result);
    300     checkEventElement(parsedResult, event);
    301
    302     int childElementCount = 0;
    303
    304     for (
    305       Node node = parsedResult.getFirstChild(); node != null;
    306         node = node.getNextSibling()) {
    307       switch (node.getNodeType()) {
    308       case Node.ELEMENT_NODE:
    309         childElementCount++;
    310
    311         if (childElementCount == 1) {
    312           checkMessageElement((Element) node, "Hello, World");
    313         } else {
    314           checkNDCElement((Element) node, "NDC goes here");
    315         }
    316
    317         break;
    318
    319       case Node.COMMENT_NODE:
    320         break;
    321
    322       case Node.TEXT_NODE:
    323
    324         //  should only be whitespace
    325         break;
    326
    327       default:
    328         fail("Unexpected node type");
    329
    330         break;
    331       }
    332     }
    333
    334     assertEquals(2, childElementCount);
    335   }
    336
    337   /**
    338    * Tests getLocationInfo and setLocationInfo.
    339    */
    340   public void testGetSetLocationInfo() {
    341     XMLLayout layout = new XMLLayout();
    342     assertEquals(false, layout.getLocationInfo());
    343     layout.setLocationInfo(true);
    344     assertEquals(true, layout.getLocationInfo());
    345     layout.setLocationInfo(false);
    346     assertEquals(false, layout.getLocationInfo());
    347   }
    348
    349   /**
    350    * Tests activateOptions().
    351    */
    352   public void testActivateOptions() {
    353     XMLLayout layout = new XMLLayout();
    354     layout.activateOptions();
    355   }
    356
    357     /**
    358      * Level with arbitrary toString value.
    359      */
    360     private static final class ProblemLevel extends Level {
    361         private static final long serialVersionUID = 1L;
    362         /**
    363          * Construct new instance.
    364          * @param levelName level name, may not be null.
    365          */
    366         public ProblemLevel(final String levelName) {
    367             super(6000, levelName, 6);
    368         }
    369     }
    370
    371     /**
    372      * Tests problematic characters in multiple fields.
    373      * @throws Exception if parser can not be constructed or source is not a valid XML document.
    374      */
    375     public void testProblemCharacters() throws Exception {
    376       String problemName = "com.example.bar<>&\"'";
    377       Logger logger = Logger.getLogger(problemName);
    378       Level level = new ProblemLevel(problemName);
    379       Exception ex = new IllegalArgumentException(problemName);
    380       String threadName = Thread.currentThread().getName();
    381       Thread.currentThread().setName(problemName);
    382       NDC.push(problemName);
    383       Hashtable mdcMap = MDC.getContext();
    384       if (mdcMap != null) {
    385           mdcMap.clear();
    386       }
    387       MDC.put(problemName, problemName);
    388       LoggingEvent event =
    389         new LoggingEvent(
    390           problemName, logger, level, problemName, ex);
    391       XMLLayout layout = (XMLLayout) createLayout();
    392       layout.setProperties(true);
    393       String result = layout.format(event);
    394       mdcMap = MDC.getContext();
    395       if (mdcMap != null) {
    396           mdcMap.clear();
    397       }
    398       Thread.currentThread().setName(threadName);
    399
    400       Element parsedResult = parse(result);
    401       checkEventElement(parsedResult, event);
    402
    403       int childElementCount = 0;
    404
    405       for (
    406         Node node = parsedResult.getFirstChild(); node != null;
    407           node = node.getNextSibling()) {
    408         switch (node.getNodeType()) {
    409         case Node.ELEMENT_NODE:
    410           childElementCount++;
    411           switch(childElementCount) {
    412               case 1:
    413               checkMessageElement((Element) node, problemName);
    414               break;
    415
    416               case 2:
    417               checkNDCElement((Element) node, problemName);
    418               break;
    419
    420               case 3:
    421               checkThrowableElement((Element) node, ex);
    422               break;
    423
    424               case 4:
    425               checkPropertiesElement((Element) node, problemName, problemName);
    426               break;
    427
    428               default:
    429               fail("Unexpected element");
    430               break;
    431           }
    432
    433           break;
    434
    435         case Node.COMMENT_NODE:
    436           break;
    437
    438         case Node.TEXT_NODE:
    439
    440           //  should only be whitespace
    441           break;
    442
    443         default:
    444           fail("Unexpected node type");
    445
    446           break;
    447         }
    448       }
    449     }
    450
    451     /**
    452       * Tests CDATA element within NDC content.  See bug 37560.
    453       */
    454     public void testNDCWithCDATA() throws Exception {
    455         Logger logger = Logger.getLogger("com.example.bar");
    456         Level level = Level.INFO;
    457         String ndcMessage ="<envelope><faultstring><![CDATA[The EffectiveDate]]></faultstring><envelope>";
    458         NDC.push(ndcMessage);
    459         LoggingEvent event =
    460           new LoggingEvent(
    461             "com.example.bar", logger, level, "Hello, World", null);
    462         Layout layout = createLayout();
    463         String result = layout.format(event);
    464         NDC.clear();
    465         Element parsedResult = parse(result);
    466         NodeList ndcs = parsedResult.getElementsByTagName("log4j:NDC");
    467         assertEquals(1, ndcs.getLength());
    468         StringBuffer buf = new StringBuffer();
    469         for(Node child = ndcs.item(0).getFirstChild();
    470                 child != null;
    471                 child = child.getNextSibling()) {
    472             buf.append(child.getNodeValue());
    473         }
    474         assertEquals(ndcMessage, buf.toString());
    475    }
    476
    477     /**
    478       * Tests CDATA element within exception.  See bug 37560.
    479       */
    480     public void testExceptionWithCDATA() throws Exception {
    481         Logger logger = Logger.getLogger("com.example.bar");
    482         Level level = Level.INFO;
    483         String exceptionMessage ="<envelope><faultstring><![CDATA[The EffectiveDate]]></faultstring><envelope>";
    484         LoggingEvent event =
    485           new LoggingEvent(
    486             "com.example.bar", logger, level, "Hello, World", new Exception(exceptionMessage));
    487         Layout layout = createLayout();
    488         String result = layout.format(event);
    489         Element parsedResult = parse(result);
    490         NodeList throwables = parsedResult.getElementsByTagName("log4j:throwable");
    491         assertEquals(1, throwables.getLength());
    492         StringBuffer buf = new StringBuffer();
    493         for(Node child = throwables.item(0).getFirstChild();
    494                 child != null;
    495                 child = child.getNextSibling()) {
    496             buf.append(child.getNodeValue());
    497         }
    498         assertTrue(buf.toString().indexOf(exceptionMessage) != -1);
    499    }
    500
    501 }