| /* |
| * 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 java.awt.Component; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.LineNumberReader; |
| import java.io.StringReader; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Vector; |
| import java.util.zip.ZipInputStream; |
| |
| 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.helpers.UtilLoggingLevel; |
| import org.apache.log4j.spi.Decoder; |
| import org.apache.log4j.spi.LoggingEvent; |
| import org.apache.log4j.spi.ThrowableInformation; |
| import org.apache.log4j.spi.LocationInfo; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| import org.xml.sax.InputSource; |
| |
| |
| /** |
| * Decodes JDK 1.4's java.util.logging package events |
| * delivered via XML (using the logger.dtd). |
| * |
| * @author Scott Deboy (sdeboy@apache.org) |
| * @author Paul Smith (psmith@apache.org) |
| * |
| */ |
| public class UtilLoggingXMLDecoder implements Decoder { |
| //NOTE: xml section is only handed on first delivery of events |
| //on this first delivery of events, there is no end tag for the log element |
| /** |
| * Document prolog. |
| */ |
| private static final String BEGIN_PART = |
| "<log>"; |
| /** |
| * Document close. |
| */ |
| private static final String END_PART = "</log>"; |
| /** |
| * Document builder. |
| */ |
| private DocumentBuilder docBuilder; |
| /** |
| * Additional properties. |
| */ |
| private Map additionalProperties = new HashMap(); |
| /** |
| * Partial event. |
| */ |
| private String partialEvent; |
| /** |
| * Record end. |
| */ |
| private static final String RECORD_END = "</record>"; |
| /** |
| * Owner. |
| */ |
| private Component owner = null; |
| |
| private static final String ENCODING = "UTF-8"; |
| |
| /** |
| * Create new instance. |
| * @param o owner |
| */ |
| public UtilLoggingXMLDecoder(final Component o) { |
| this(); |
| this.owner = o; |
| } |
| |
| /** |
| * Create new instance. |
| */ |
| public UtilLoggingXMLDecoder() { |
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); |
| dbf.setValidating(false); |
| |
| try { |
| docBuilder = dbf.newDocumentBuilder(); |
| docBuilder.setErrorHandler(new SAXErrorHandler()); |
| docBuilder.setEntityResolver(new UtilLoggingEntityResolver()); |
| } 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 properties additional properties |
| */ |
| public void setAdditionalProperties(final Map properties) { |
| this.additionalProperties = properties; |
| } |
| |
| /** |
| * Converts the LoggingEvent data in XML string format into an actual |
| * XML Document class instance. |
| * @param data XML fragment |
| * @return dom document |
| */ |
| private Document parse(final 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. |
| |
| /** |
| * 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); |
| |
| if (!data.startsWith("<?xml")) { |
| buf.append(BEGIN_PART); |
| } |
| |
| buf.append(data); |
| |
| if (!data.endsWith(END_PART)) { |
| buf.append(END_PART); |
| } |
| |
| InputSource inputSource = |
| new InputSource(new StringReader(buf.toString())); |
| 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 if IO error during processing. |
| */ |
| public Vector decode(final URL url) throws IOException { |
| LineNumberReader reader; |
| boolean isZipFile = url.getPath().toLowerCase().endsWith(".zip"); |
| InputStream inputStream; |
| if (isZipFile) { |
| inputStream = new ZipInputStream(url.openStream()); |
| //move stream to next entry so we can read it |
| ((ZipInputStream)inputStream).getNextEntry(); |
| } else { |
| inputStream = url.openStream(); |
| } |
| if (owner != null) { |
| reader = new LineNumberReader( |
| new InputStreamReader( |
| new ProgressMonitorInputStream(owner, |
| "Loading " + url , inputStream), ENCODING)); |
| } else { |
| reader = new LineNumberReader(new InputStreamReader(inputStream, ENCODING)); |
| } |
| Vector v = new Vector(); |
| |
| String line; |
| Vector events; |
| 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; |
| } |
| |
| /** |
| * Decodes a String representing a number of events into a |
| * Vector of LoggingEvents. |
| * @param document to decode events from |
| * @return Vector of LoggingEvents |
| */ |
| public Vector decodeEvents(final String document) { |
| |
| if (document != null) { |
| |
| if (document.trim().equals("")) { |
| return null; |
| } |
| |
| String newDoc; |
| String newPartialEvent = null; |
| //separate the string into the last portion ending with </record> |
| // (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 XML fragment |
| * @return a single LoggingEvent or null |
| */ |
| public LoggingEvent decode(final 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 XML document |
| * @return Vector of LoggingEvents |
| */ |
| private Vector decodeEvents(final Document document) { |
| Vector events = new Vector(); |
| |
| NodeList eventList = document.getElementsByTagName("record"); |
| |
| for (int eventIndex = 0; eventIndex < eventList.getLength(); |
| eventIndex++) { |
| Node eventNode = eventList.item(eventIndex); |
| |
| Logger logger = null; |
| long timeStamp = 0L; |
| Level 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 = new Hashtable(); |
| |
| //format of date: 2003-05-04T11:04:52 |
| //ignore date or set as a property? using millis in constructor instead |
| NodeList list = eventNode.getChildNodes(); |
| int listLength = list.getLength(); |
| |
| if (listLength == 0) { |
| continue; |
| } |
| |
| for (int y = 0; y < listLength; y++) { |
| String tagName = list.item(y).getNodeName(); |
| |
| if (tagName.equalsIgnoreCase("logger")) { |
| logger = Logger.getLogger(getCData(list.item(y))); |
| } |
| |
| if (tagName.equalsIgnoreCase("millis")) { |
| timeStamp = Long.parseLong(getCData(list.item(y))); |
| } |
| |
| if (tagName.equalsIgnoreCase("level")) { |
| level = UtilLoggingLevel.toLevel(getCData(list.item(y))); |
| } |
| |
| if (tagName.equalsIgnoreCase("thread")) { |
| threadName = getCData(list.item(y)); |
| } |
| |
| if (tagName.equalsIgnoreCase("sequence")) { |
| properties.put("log4jid", getCData(list.item(y))); |
| } |
| |
| if (tagName.equalsIgnoreCase("message")) { |
| message = getCData(list.item(y)); |
| } |
| |
| if (tagName.equalsIgnoreCase("class")) { |
| className = getCData(list.item(y)); |
| } |
| |
| if (tagName.equalsIgnoreCase("method")) { |
| methodName = getCData(list.item(y)); |
| } |
| |
| if (tagName.equalsIgnoreCase("exception")) { |
| ArrayList exceptionList = new ArrayList(); |
| NodeList exList = list.item(y).getChildNodes(); |
| int exlistLength = exList.getLength(); |
| |
| for (int i2 = 0; i2 < exlistLength; i2++) { |
| Node exNode = exList.item(i2); |
| String exName = exList.item(i2).getNodeName(); |
| |
| if (exName.equalsIgnoreCase("message")) { |
| exceptionList.add(getCData(exList.item(i2))); |
| } |
| |
| if (exName.equalsIgnoreCase("frame")) { |
| NodeList exList2 = exNode.getChildNodes(); |
| int exlist2Length = exList2.getLength(); |
| |
| for (int i3 = 0; i3 < exlist2Length; i3++) { |
| exceptionList.add(getCData(exList2.item(i3)) + "\n"); |
| } |
| } |
| } |
| |
| exception = |
| (String[]) exceptionList.toArray(new String[exceptionList.size()]); |
| } |
| } |
| |
| /** |
| * We add all the additional properties to the properties |
| * hashtable. Override properties that already exist |
| */ |
| if (additionalProperties.size() > 0) { |
| if (properties == null) { |
| properties = new Hashtable(additionalProperties); |
| } |
| Iterator i = additionalProperties.entrySet().iterator(); |
| while (i.hasNext()) { |
| Map.Entry e = (Map.Entry) i.next(); |
| properties.put(e.getKey(), e.getValue()); |
| } |
| } |
| |
| LocationInfo info; |
| 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(null, |
| logger, timeStamp, level, message, |
| threadName, |
| new ThrowableInformation(exception), |
| ndc, |
| info, |
| properties); |
| |
| events.add(loggingEvent); |
| |
| } |
| return events; |
| } |
| |
| /** |
| * Get contents of CDATASection. |
| * @param n CDATASection |
| * @return text content of all text or CDATA children of node. |
| */ |
| private String getCData(final 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(); |
| } |
| } |