001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 2014, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.log4j;
015
016import java.io.ByteArrayInputStream;
017import java.io.InputStream;
018import java.util.Iterator;
019
020import javax.xml.XMLConstants;
021import javax.xml.namespace.NamespaceContext;
022import javax.xml.parsers.DocumentBuilder;
023import javax.xml.parsers.DocumentBuilderFactory;
024import javax.xml.xpath.XPath;
025import javax.xml.xpath.XPathConstants;
026import javax.xml.xpath.XPathFactory;
027
028import org.apache.log4j.MDC;
029import org.junit.After;
030import org.junit.Assert;
031import org.junit.Before;
032import org.junit.Test;
033import org.w3c.dom.Document;
034import org.w3c.dom.Node;
035import org.w3c.dom.NodeList;
036import org.xml.sax.EntityResolver;
037import org.xml.sax.InputSource;
038
039import ch.qos.logback.classic.Level;
040import ch.qos.logback.classic.Logger;
041import ch.qos.logback.classic.LoggerContext;
042import ch.qos.logback.classic.spi.ILoggingEvent;
043import ch.qos.logback.classic.spi.LoggingEvent;
044
045/**
046 * A test for correct (well-formed, valid) log4j XML layout.
047 * 
048 * @author Gabriel Corona
049 */
050public class XMLLayoutTest {
051
052    private static final String DOCTYPE = "<!DOCTYPE log4j:eventSet SYSTEM \"http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd\">";
053    private static final String NAMESPACE = "http://jakarta.apache.org/log4j/";
054    private static final String DTD_URI = "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd";
055
056    private static final String MDC_KEY = "key <&>'\"]]>";
057    private static final String MDC_VALUE = "value <&>'\"]]>";
058
059    private static final String MESSAGE = "test message, <&>'\"";
060
061    private LoggerContext lc;
062    private Logger root;
063    private XMLLayout layout;
064
065    @Before
066    public void setUp() throws Exception {
067        lc = new LoggerContext();
068        lc.setName("default");
069
070        layout = new XMLLayout();
071        layout.setLocationInfo(true);
072        layout.setContext(lc);
073        layout.setProperties(true);
074        layout.setLocationInfo(true);
075        layout.start();
076
077        root = lc.getLogger(Logger.ROOT_LOGGER_NAME);
078
079    }
080
081    @After
082    public void tearDown() throws Exception {
083        lc = null;
084        layout = null;
085        MDC.clear();
086    }
087
088    @Test
089    public void testDoLayout() throws Exception {
090        ILoggingEvent le = createLoggingEvent();
091
092        String result = DOCTYPE + "<log4j:eventSet xmlns:log4j='http://jakarta.apache.org/log4j/'>";
093        if (layout.getFileHeader() != null) {
094            result += layout.getFileHeader();
095        }
096        if (layout.getPresentationHeader() != null) {
097            result += layout.getPresentationHeader();
098        }
099        result += layout.doLayout(le);
100        if (layout.getPresentationFooter() != null) {
101            result += layout.getPresentationFooter();
102        }
103        if (layout.getFileFooter() != null) {
104            result += layout.getFileFooter();
105        }
106        result += "</log4j:eventSet>";
107
108        Document document = parse(result);
109
110        XPath xpath = this.newXPath();
111
112        // Test log4j:event:
113        NodeList eventNodes = (NodeList) xpath.compile("//log4j:event").evaluate(document, XPathConstants.NODESET);
114        Assert.assertEquals(1, eventNodes.getLength());
115
116        // Test log4g:message:
117        Assert.assertEquals(MESSAGE, xpath.compile("//log4j:message").evaluate(document, XPathConstants.STRING));
118
119        // Test log4j:data:
120        NodeList dataNodes = (NodeList) xpath.compile("//log4j:data").evaluate(document, XPathConstants.NODESET);
121        boolean foundMdc = false;
122        for (int i = 0; i != dataNodes.getLength(); ++i) {
123            Node dataNode = dataNodes.item(i);
124            if (dataNode.getAttributes().getNamedItem("name").getNodeValue().equals(MDC_KEY)) {
125                foundMdc = true;
126                Assert.assertEquals(MDC_VALUE, dataNode.getAttributes().getNamedItem("value").getNodeValue());
127                break;
128            }
129        }
130        Assert.assertTrue(foundMdc);
131    }
132
133    /**
134    * Create a XPath instance with xmlns:log4j="http://jakarta.apache.org/log4j/"
135    * 
136    * @return XPath instance with log4 namespace
137    */
138    private XPath newXPath() {
139        XPathFactory xPathfactory = XPathFactory.newInstance();
140        XPath xpath = xPathfactory.newXPath();
141
142        xpath.setNamespaceContext(new NamespaceContext() {
143            @SuppressWarnings("rawtypes")
144            public Iterator getPrefixes(String namespaceURI) {
145                throw new UnsupportedOperationException();
146            }
147
148            public String getPrefix(String namespaceURI) {
149                throw new UnsupportedOperationException();
150            }
151
152            public String getNamespaceURI(String prefix) {
153                if ("log4j".equals(prefix)) {
154                    return NAMESPACE;
155                } else {
156                    return XMLConstants.NULL_NS_URI;
157                }
158            }
159        });
160
161        return xpath;
162    }
163
164    private LoggingEvent createLoggingEvent() {
165        MDC.put(MDC_KEY, MDC_VALUE);
166        LoggingEvent event = new LoggingEvent("com.example.XMLLayoutTest-<&>'\"]]>", root, Level.DEBUG, MESSAGE, new RuntimeException(
167                        "Dummy exception: <&>'\"]]>"), null);
168        event.setThreadName("Dummy thread <&>'\"");
169
170        StackTraceElement ste1 = new StackTraceElement("c1", "m1", "f1", 1);
171        StackTraceElement ste2 = new StackTraceElement("c2", "m2", "f2", 2);
172        event.setCallerData(new StackTraceElement[] { ste1, ste2 });
173
174        return event;
175    }
176
177    /**
178     * Parse and validate Log4j XML
179     * 
180     * @param output Log4j XML
181     * @return Document
182     * @throws Exception
183     */
184    private Document parse(String output) throws Exception {
185
186        // Lookup the DTD in log4j.jar:
187        EntityResolver resolver = new EntityResolver() {
188            public InputSource resolveEntity(String publicId, String systemId) {
189                if (publicId == null && systemId != null && systemId.equals(DTD_URI)) {
190                    final String path = "/org/apache/log4j/xml/log4j.dtd";
191                    InputStream in = this.getClass().getResourceAsStream(path);
192                    return new InputSource(in);
193                } else {
194                    throw new RuntimeException("Not found");
195                }
196            }
197        };
198
199        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
200        factory.setNamespaceAware(true);
201        factory.setValidating(true);
202
203        DocumentBuilder builder = factory.newDocumentBuilder();
204        builder.setEntityResolver(resolver);
205
206        return builder.parse(new ByteArrayInputStream(output.getBytes("UTF-8")));
207    }
208
209}