| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * Licensed 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 java.awt.Component; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.LineNumberReader; |
| import java.io.StringReader; |
| import java.net.URL; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| import javax.swing.ProgressMonitorInputStream; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.apache.log4j.Level; |
| import org.apache.log4j.Logger; |
| import org.apache.log4j.spi.Decoder; |
| import org.apache.log4j.spi.LoggingEvent; |
| import org.apache.log4j.spi.ThrowableInformation; |
| import org.apache.log4j.spi.location.LocationInfo; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| |
| |
| /** |
| * Decodes Logging Events in XML formated into elements that are used by |
| * Chainsaw. |
| * |
| * This decoder can process a collection of log4j:event nodes ONLY |
| * (no XML declaration nor eventSet node) |
| * |
| * NOTE: Only a single LoggingEvent is returned from the decode method |
| * even though the DTD supports multiple events nested in an eventSet. |
| * |
| * @author Scott Deboy <sdeboy@apache.org> |
| * @author Paul Smith <psmith@apache.org> |
| * |
| */ |
| public class XMLDecoder implements Decoder { |
| private static final String BEGINPART = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><!DOCTYPE log4j:eventSet SYSTEM \"log4j.dtd\"><log4j:eventSet version=\"1.2\" xmlns:log4j=\"http://jakarta.apache.org/log4j/\">"; |
| private static final String ENDPART = "</log4j:eventSet>"; |
| private static final String RECORD_END = "</log4j:event>"; |
| private DocumentBuilderFactory dbf; |
| private DocumentBuilder docBuilder; |
| private Map additionalProperties = new HashMap(); |
| private String partialEvent; |
| private Component owner = null; |
| |
| public XMLDecoder(Component owner) { |
| this(); |
| this.owner = owner; |
| } |
| public XMLDecoder() { |
| dbf = DocumentBuilderFactory.newInstance(); |
| dbf.setValidating(false); |
| |
| try { |
| docBuilder = dbf.newDocumentBuilder(); |
| docBuilder.setErrorHandler(new SAXErrorHandler()); |
| docBuilder.setEntityResolver(new Log4jEntityResolver()); |
| } catch (ParserConfigurationException pce) { |
| System.err.println("Unable to get document builder"); |
| } |
| } |
| |
| /** |
| * Sets an additionalProperty map, where each Key/Value pair is |
| * automatically added to each LoggingEvent as it is decoded. |
| * |
| * This is useful, say, to include the source file name of the Logging events |
| * @param additionalProperties |
| */ |
| public void setAdditionalProperties(Map additionalProperties) { |
| this.additionalProperties = additionalProperties; |
| } |
| |
| /** |
| * Converts the LoggingEvent data in XML string format into an actual |
| * XML Document class instance. |
| * @param data |
| * @return dom document |
| */ |
| private Document parse(String data) { |
| if (docBuilder == null || data == null) { |
| return null; |
| } |
| Document document = null; |
| |
| try { |
| // we change the system ID to a valid URI so that Crimson won't |
| // complain. Indeed, "log4j.dtd" alone is not a valid URI which |
| // causes Crimson to barf. The Log4jEntityResolver only cares |
| // about the "log4j.dtd" ending. |
| // buf.setLength(0); |
| |
| /** |
| * resetting the length of the StringBuffer is dangerous, particularly |
| * on some JDK 1.4 impls, there's a known Bug that causes a memory leak |
| */ |
| StringBuffer buf = new StringBuffer(1024); |
| |
| buf.append(BEGINPART); |
| buf.append(data); |
| buf.append(ENDPART); |
| |
| InputSource inputSource = |
| new InputSource(new StringReader(buf.toString())); |
| inputSource.setSystemId("dummy://log4j.dtd"); |
| document = docBuilder.parse(inputSource); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| |
| return document; |
| } |
| |
| /** |
| * Decodes a File into a Vector of LoggingEvents |
| * @param url the url of a file containing events to decode |
| * @return Vector of LoggingEvents |
| * @throws IOException |
| */ |
| public Vector decode(URL url) throws IOException { |
| LineNumberReader reader = null; |
| if (owner != null) { |
| reader = new LineNumberReader(new InputStreamReader(new ProgressMonitorInputStream(owner, "Loading " + url , url.openStream()))); |
| } else { |
| reader = new LineNumberReader(new InputStreamReader(url.openStream())); |
| } |
| |
| Vector v = new Vector(); |
| |
| String line = null; |
| Vector events = null; |
| try { |
| while ((line = reader.readLine()) != null) { |
| StringBuffer buffer = new StringBuffer(line); |
| for (int i = 0;i<1000;i++) { |
| buffer.append(reader.readLine()).append("\n"); |
| } |
| events = decodeEvents(buffer.toString()); |
| if (events != null) |
| v.addAll(events); |
| } |
| } finally { |
| partialEvent = null; |
| try { |
| if (reader != null) { |
| reader.close(); |
| } |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| return v; |
| } |
| |
| public Vector decodeEvents(String document) { |
| if (document != null) { |
| document = document.trim(); |
| if (document.equals("")) { |
| return null; |
| } |
| String newDoc=null; |
| String newPartialEvent=null; |
| //separate the string into the last portion ending with </log4j:event> (which will |
| //be processed) and the partial event which will be combined and processed in the next section |
| |
| //if the document does not contain a record end, append it to the partial event string |
| if (document.lastIndexOf(RECORD_END) == -1) { |
| partialEvent = partialEvent + document; |
| return null; |
| } |
| |
| if (document.lastIndexOf(RECORD_END) + RECORD_END.length() < document.length()) { |
| newDoc = document.substring(0, document.lastIndexOf(RECORD_END) + RECORD_END.length()); |
| newPartialEvent = document.substring(document.lastIndexOf(RECORD_END) + RECORD_END.length()); |
| } else { |
| newDoc = document; |
| } |
| if (partialEvent != null) { |
| newDoc=partialEvent + newDoc; |
| } |
| partialEvent=newPartialEvent; |
| Document doc = parse(newDoc); |
| if (doc == null) { |
| return null; |
| } |
| return decodeEvents(doc); |
| } |
| return null; |
| } |
| |
| /** |
| * Converts the string data into an XML Document, and then soaks out the |
| * relevant bits to form a new LoggingEvent instance which can be used |
| * by any Log4j element locally. |
| * @param data |
| * @return a single LoggingEvent |
| */ |
| public LoggingEvent decode(String data) { |
| Document document = parse(data); |
| |
| if (document == null) { |
| return null; |
| } |
| |
| Vector events = decodeEvents(document); |
| |
| if (events.size() > 0) { |
| return (LoggingEvent) events.firstElement(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Given a Document, converts the XML into a Vector of LoggingEvents |
| * @param document |
| * @return Vector of LoggingEvents |
| */ |
| private Vector decodeEvents(Document document) { |
| Vector events = new Vector(); |
| |
| Logger logger = null; |
| long timeStamp = 0L; |
| String level = null; |
| String threadName = null; |
| Object message = null; |
| String ndc = null; |
| String[] exception = null; |
| String className = null; |
| String methodName = null; |
| String fileName = null; |
| String lineNumber = null; |
| Hashtable properties = null; |
| |
| NodeList nl = document.getElementsByTagName("log4j:eventSet"); |
| Node eventSet = nl.item(0); |
| |
| NodeList eventList = eventSet.getChildNodes(); |
| |
| for (int eventIndex = 0; eventIndex < eventList.getLength(); |
| eventIndex++) { |
| Node eventNode = eventList.item(eventIndex); |
| //ignore carriage returns in xml |
| if(eventNode.getNodeType() != Node.ELEMENT_NODE) { |
| continue; |
| } |
| logger = |
| Logger.getLogger( |
| eventNode.getAttributes().getNamedItem("logger").getNodeValue()); |
| timeStamp = |
| Long.parseLong( |
| eventNode.getAttributes().getNamedItem("timestamp").getNodeValue()); |
| level =eventNode.getAttributes().getNamedItem("level").getNodeValue(); |
| threadName = |
| eventNode.getAttributes().getNamedItem("thread").getNodeValue(); |
| |
| NodeList list = eventNode.getChildNodes(); |
| int listLength = list.getLength(); |
| |
| for (int y = 0; y < listLength; y++) { |
| String tagName = list.item(y).getNodeName(); |
| |
| if (tagName.equalsIgnoreCase("log4j:message")) { |
| message = getCData(list.item(y)); |
| } |
| |
| if (tagName.equalsIgnoreCase("log4j:NDC")) { |
| ndc = getCData(list.item(y)); |
| } |
| //still support receiving of MDC and convert to properties |
| if (tagName.equalsIgnoreCase("log4j:MDC")) { |
| properties = new Hashtable(); |
| NodeList propertyList = list.item(y).getChildNodes(); |
| int propertyLength = propertyList.getLength(); |
| |
| for (int i = 0; i < propertyLength; i++) { |
| String propertyTag = propertyList.item(i).getNodeName(); |
| |
| if (propertyTag.equalsIgnoreCase("log4j:data")) { |
| Node property = propertyList.item(i); |
| String name = |
| property.getAttributes().getNamedItem("name").getNodeValue(); |
| String value = |
| property.getAttributes().getNamedItem("value").getNodeValue(); |
| properties.put(name, value); |
| } |
| } |
| } |
| |
| if (tagName.equalsIgnoreCase("log4j:throwable")) { |
| exception = new String[] { getCData(list.item(y)) }; |
| } |
| |
| if (tagName.equalsIgnoreCase("log4j:locationinfo")) { |
| className = |
| list.item(y).getAttributes().getNamedItem("class").getNodeValue(); |
| methodName = |
| list.item(y).getAttributes().getNamedItem("method").getNodeValue(); |
| fileName = |
| list.item(y).getAttributes().getNamedItem("file").getNodeValue(); |
| lineNumber = |
| list.item(y).getAttributes().getNamedItem("line").getNodeValue(); |
| } |
| |
| if (tagName.equalsIgnoreCase("log4j:properties")) { |
| if (properties == null) { |
| properties = new Hashtable(); |
| } |
| NodeList propertyList = list.item(y).getChildNodes(); |
| int propertyLength = propertyList.getLength(); |
| |
| for (int i = 0; i < propertyLength; i++) { |
| String propertyTag = propertyList.item(i).getNodeName(); |
| |
| if (propertyTag.equalsIgnoreCase("log4j:data")) { |
| Node property = propertyList.item(i); |
| String name = |
| property.getAttributes().getNamedItem("name").getNodeValue(); |
| String value = |
| property.getAttributes().getNamedItem("value").getNodeValue(); |
| properties.put(name, value); |
| } |
| } |
| } |
| |
| /** |
| * We add all the additional properties to the properties |
| * hashtable. Don't override properties that already exist |
| */ |
| if (additionalProperties.size() > 0) { |
| if (properties == null) { |
| properties = new Hashtable(additionalProperties); |
| } else { |
| Iterator i = additionalProperties.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry e = (Map.Entry) i.next(); |
| if (!(properties.containsKey(e.getKey()))) { |
| properties.put(e.getKey(), e.getValue()); |
| } |
| } |
| } |
| } |
| } |
| Level levelImpl = Level.toLevel(level); |
| |
| LocationInfo info = null; |
| if ((fileName != null) || (className != null) || (methodName != null) || (lineNumber != null)) { |
| info = new LocationInfo(fileName, className, methodName, lineNumber); |
| } else { |
| info = LocationInfo.NA_LOCATION_INFO; |
| } |
| if (exception == null) { |
| exception = new String[]{""}; |
| } |
| |
| LoggingEvent loggingEvent = new LoggingEvent(); |
| loggingEvent.setLogger(logger); |
| loggingEvent.setTimeStamp(timeStamp); |
| loggingEvent.setLevel(levelImpl); |
| loggingEvent.setThreadName(threadName); |
| loggingEvent.setMessage(message); |
| loggingEvent.setNDC(ndc); |
| loggingEvent.setThrowableInformation(new ThrowableInformation(exception)); |
| loggingEvent.setLocationInformation(info); |
| loggingEvent.setProperties(properties); |
| |
| events.add(loggingEvent); |
| |
| logger = null; |
| timeStamp = 0L; |
| level = null; |
| threadName = null; |
| message = null; |
| ndc = null; |
| exception = null; |
| className = null; |
| methodName = null; |
| fileName = null; |
| lineNumber = null; |
| properties = null; |
| } |
| |
| return events; |
| } |
| |
| private String getCData(Node n) { |
| StringBuffer buf = new StringBuffer(); |
| NodeList nl = n.getChildNodes(); |
| |
| for (int x = 0; x < nl.getLength(); x++) { |
| Node innerNode = nl.item(x); |
| |
| if ( |
| (innerNode.getNodeType() == Node.TEXT_NODE) |
| || (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) { |
| buf.append(innerNode.getNodeValue()); |
| } |
| } |
| |
| return buf.toString(); |
| } |
| } |