View Javadoc

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 java.awt.Component;
21  import java.io.IOException;
22  import java.io.InputStreamReader;
23  import java.io.LineNumberReader;
24  import java.io.StringReader;
25  import java.net.URL;
26  import java.util.HashMap;
27  import java.util.Hashtable;
28  import java.util.Iterator;
29  import java.util.Map;
30  import java.util.Vector;
31  
32  import javax.swing.ProgressMonitorInputStream;
33  import javax.xml.parsers.DocumentBuilder;
34  import javax.xml.parsers.DocumentBuilderFactory;
35  import javax.xml.parsers.ParserConfigurationException;
36  
37  import org.apache.log4j.Level;
38  import org.apache.log4j.Logger;
39  import org.apache.log4j.spi.Decoder;
40  import org.apache.log4j.spi.LoggingEvent;
41  import org.apache.log4j.spi.ThrowableInformation;
42  import org.apache.log4j.spi.LocationInfo;
43  import org.w3c.dom.Document;
44  import org.w3c.dom.Node;
45  import org.w3c.dom.NodeList;
46  import org.xml.sax.InputSource;
47  
48  
49  /***
50   * Decodes Logging Events in XML formated into elements that are used by
51   * Chainsaw.
52   *
53   * This decoder can process a collection of log4j:event nodes ONLY
54   * (no XML declaration nor eventSet node)
55   *
56   * NOTE:  Only a single LoggingEvent is returned from the decode method
57   * even though the DTD supports multiple events nested in an eventSet.
58   *
59   *
60   * @author Scott Deboy (sdeboy@apache.org)
61   * @author Paul Smith (psmith@apache.org)
62   *
63   */
64  public class XMLDecoder implements Decoder {
65      /***
66       * Document prolog.
67       */
68    private static final String BEGINPART =
69      "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>"
70      + "<!DOCTYPE log4j:eventSet SYSTEM \"http://localhost/log4j.dtd\">"
71      + "<log4j:eventSet version=\"1.2\" "
72      + "xmlns:log4j=\"http://jakarta.apache.org/log4j/\">";
73      /***
74       * Document close.
75       */
76    private static final String ENDPART = "</log4j:eventSet>";
77      /***
78       * Record end.
79       */
80    private static final String RECORD_END = "</log4j:event>";
81  
82      /***
83       * Document builder.
84       */
85    private DocumentBuilder docBuilder;
86      /***
87       * Additional properties.
88       */
89    private Map additionalProperties = new HashMap();
90      /***
91       * Partial event.
92       */
93    private String partialEvent;
94      /***
95       * Owner.
96       */
97    private Component owner = null;
98  
99      /***
100      * Create new instance.
101      * @param o owner
102      */
103   public XMLDecoder(final Component o) {
104       this();
105       this.owner = o;
106   }
107 
108     /***
109      * Create new instance.
110      */
111    public XMLDecoder() {
112     super();
113     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
114     dbf.setValidating(false);
115 
116     try {
117       docBuilder = dbf.newDocumentBuilder();
118       docBuilder.setErrorHandler(new SAXErrorHandler());
119       docBuilder.setEntityResolver(new Log4jEntityResolver());
120     } catch (ParserConfigurationException pce) {
121       System.err.println("Unable to get document builder");
122     }
123   }
124 
125   /***
126    * Sets an additionalProperty map, where each Key/Value pair is
127    * automatically added to each LoggingEvent as it is decoded.
128    *
129    * This is useful, say, to include the source file name of the Logging events
130    * @param properties additional properties
131    */
132   public void setAdditionalProperties(final Map properties) {
133     this.additionalProperties = properties;
134   }
135 
136   /***
137    * Converts the LoggingEvent data in XML string format into an actual
138    * XML Document class instance.
139    * @param data XML fragment
140    * @return dom document
141    */
142   private Document parse(final String data) {
143     if (docBuilder == null || data == null) {
144       return null;
145     }
146     Document document = null;
147 
148     try {
149       // we change the system ID to a valid URI so that Crimson won't
150       // complain. Indeed, "log4j.dtd" alone is not a valid URI which
151       // causes Crimson to barf. The Log4jEntityResolver only cares
152       // about the "log4j.dtd" ending.
153       //      buf.setLength(0);
154 
155       /***
156        * resetting the length of the StringBuffer is dangerous, particularly
157        * on some JDK 1.4 impls, there's a known Bug that causes a memory leak
158        */
159       StringBuffer buf = new StringBuffer(1024);
160 
161       buf.append(BEGINPART);
162       buf.append(data);
163       buf.append(ENDPART);
164 
165       InputSource inputSource =
166         new InputSource(new StringReader(buf.toString()));
167       document = docBuilder.parse(inputSource);
168     } catch (Exception e) {
169       e.printStackTrace();
170     }
171 
172     return document;
173   }
174 
175   /***
176    * Decodes a File into a Vector of LoggingEvents.
177    * @param url the url of a file containing events to decode
178    * @return Vector of LoggingEvents
179    * @throws IOException if IO error during processing.
180    */
181   public Vector decode(final URL url) throws IOException {
182     LineNumberReader reader;
183     if (owner != null) {
184       reader = new LineNumberReader(new InputStreamReader(
185               new ProgressMonitorInputStream(owner,
186                       "Loading " + url , url.openStream())));
187     } else {
188       reader = new LineNumberReader(new InputStreamReader(url.openStream()));
189     }
190 
191     Vector v = new Vector();
192 
193     String line;
194     Vector events;
195     try {
196         while ((line = reader.readLine()) != null) {
197             StringBuffer buffer = new StringBuffer(line);
198             for (int i = 0; i < 1000; i++) {
199                 buffer.append(reader.readLine()).append("\n");
200             }
201             events = decodeEvents(buffer.toString());
202             if (events != null) {
203                 v.addAll(events);
204             }
205         }
206     } finally {
207       partialEvent = null;
208       try {
209         if (reader != null) {
210           reader.close();
211         }
212       } catch (Exception e) {
213         e.printStackTrace();
214       }
215     }
216     return v;
217   }
218 
219     /***
220      * Decodes a String representing a number of events into a
221      * Vector of LoggingEvents.
222      * @param document to decode events from
223      * @return Vector of LoggingEvents
224      */
225   public Vector decodeEvents(final String document) {
226     if (document != null) {
227       if (document.trim().equals("")) {
228         return null;
229       }
230         String newDoc = null;
231         String newPartialEvent = null;
232         //separate the string into the last portion ending with
233         // </log4j:event> (which will be processed) and the
234         // partial event which will be combined and
235         // processed in the next section
236 
237         //if the document does not contain a record end,
238         // append it to the partial event string
239         if (document.lastIndexOf(RECORD_END) == -1) {
240             partialEvent = partialEvent + document;
241             return null;
242         }
243 
244         if (document.lastIndexOf(RECORD_END)
245                 + RECORD_END.length() < document.length()) {
246             newDoc = document.substring(0,
247                     document.lastIndexOf(RECORD_END) + RECORD_END.length());
248             newPartialEvent = document.substring(
249                     document.lastIndexOf(RECORD_END) + RECORD_END.length());
250         } else {
251             newDoc = document;
252         }
253         if (partialEvent != null) {
254             newDoc = partialEvent + newDoc;
255         }
256         partialEvent = newPartialEvent;
257         Document doc = parse(newDoc);
258         if (doc == null) {
259             return null;
260         }
261         return decodeEvents(doc);
262     }
263     return null;
264   }
265 
266   /***
267    * Converts the string data into an XML Document, and then soaks out the
268    * relevant bits to form a new LoggingEvent instance which can be used
269    * by any Log4j element locally.
270    * @param data XML fragment
271    * @return a single LoggingEvent
272    */
273   public LoggingEvent decode(final String data) {
274     Document document = parse(data);
275 
276     if (document == null) {
277       return null;
278     }
279 
280     Vector events = decodeEvents(document);
281 
282     if (events.size() > 0) {
283       return (LoggingEvent) events.firstElement();
284     }
285 
286     return null;
287   }
288 
289   /***
290    * Given a Document, converts the XML into a Vector of LoggingEvents.
291    * @param document XML document
292    * @return Vector of LoggingEvents
293    */
294   private Vector decodeEvents(final Document document) {
295     Vector events = new Vector();
296 
297     Logger logger;
298     long timeStamp;
299     String level;
300     String threadName;
301     Object message = null;
302     String ndc = null;
303     String[] exception = null;
304     String className = null;
305     String methodName = null;
306     String fileName = null;
307     String lineNumber = null;
308     Hashtable properties = null;
309 
310     NodeList nl = document.getElementsByTagName("log4j:eventSet");
311     Node eventSet = nl.item(0);
312 
313     NodeList eventList = eventSet.getChildNodes();
314 
315     for (int eventIndex = 0; eventIndex < eventList.getLength();
316         eventIndex++) {
317         Node eventNode = eventList.item(eventIndex);
318       //ignore carriage returns in xml
319         if (eventNode.getNodeType() != Node.ELEMENT_NODE) {
320             continue;
321         }
322         logger =
323             Logger.getLogger(
324                eventNode.getAttributes().getNamedItem("logger").getNodeValue());
325       timeStamp =
326         Long.parseLong(
327           eventNode.getAttributes().getNamedItem("timestamp").getNodeValue());
328       level = eventNode.getAttributes().getNamedItem("level").getNodeValue();
329       threadName =
330         eventNode.getAttributes().getNamedItem("thread").getNodeValue();
331 
332       NodeList list = eventNode.getChildNodes();
333       int listLength = list.getLength();
334 
335       for (int y = 0; y < listLength; y++) {
336         String tagName = list.item(y).getNodeName();
337 
338         if (tagName.equalsIgnoreCase("log4j:message")) {
339           message = getCData(list.item(y));
340         }
341 
342         if (tagName.equalsIgnoreCase("log4j:NDC")) {
343           ndc = getCData(list.item(y));
344         }
345         //still support receiving of MDC and convert to properties
346         if (tagName.equalsIgnoreCase("log4j:MDC")) {
347           properties = new Hashtable();
348           NodeList propertyList = list.item(y).getChildNodes();
349           int propertyLength = propertyList.getLength();
350 
351           for (int i = 0; i < propertyLength; i++) {
352             String propertyTag = propertyList.item(i).getNodeName();
353 
354             if (propertyTag.equalsIgnoreCase("log4j:data")) {
355               Node property = propertyList.item(i);
356               String name =
357                 property.getAttributes().getNamedItem("name").getNodeValue();
358               String value =
359                 property.getAttributes().getNamedItem("value").getNodeValue();
360               properties.put(name, value);
361             }
362           }
363         }
364 
365         if (tagName.equalsIgnoreCase("log4j:throwable")) {
366           exception = new String[] {
367                   getCData(list.item(y))
368           };
369         }
370 
371         if (tagName.equalsIgnoreCase("log4j:locationinfo")) {
372           className =
373             list.item(y).getAttributes().getNamedItem("class").getNodeValue();
374           methodName =
375             list.item(y).getAttributes().getNamedItem("method").getNodeValue();
376           fileName =
377             list.item(y).getAttributes().getNamedItem("file").getNodeValue();
378           lineNumber =
379             list.item(y).getAttributes().getNamedItem("line").getNodeValue();
380         }
381 
382         if (tagName.equalsIgnoreCase("log4j:properties")) {
383           if (properties == null) {
384               properties = new Hashtable();
385           }
386           NodeList propertyList = list.item(y).getChildNodes();
387           int propertyLength = propertyList.getLength();
388 
389           for (int i = 0; i < propertyLength; i++) {
390             String propertyTag = propertyList.item(i).getNodeName();
391 
392             if (propertyTag.equalsIgnoreCase("log4j:data")) {
393               Node property = propertyList.item(i);
394               String name =
395                 property.getAttributes().getNamedItem("name").getNodeValue();
396               String value =
397                 property.getAttributes().getNamedItem("value").getNodeValue();
398               properties.put(name, value);
399             }
400           }
401         }
402 
403         /***
404          * We add all the additional properties to the properties
405          * hashtable.  Don't override properties that already exist
406          */
407         if (additionalProperties.size() > 0) {
408           if (properties == null) {
409             properties = new Hashtable(additionalProperties);
410           } else {
411             Iterator i = additionalProperties.entrySet().iterator();
412             while (i.hasNext()) {
413               Map.Entry e = (Map.Entry) i.next();
414               if (!(properties.containsKey(e.getKey()))) {
415                 properties.put(e.getKey(), e.getValue());
416               }
417             }
418           }
419         }
420       }
421       Level levelImpl = Level.toLevel(level);
422 
423       LocationInfo info;
424       if ((fileName != null)
425               || (className != null)
426               || (methodName != null)
427               || (lineNumber != null)) {
428           info = new LocationInfo(fileName, className, methodName, lineNumber);
429       } else {
430         info = LocationInfo.NA_LOCATION_INFO;
431       }
432       if (exception == null) {
433           exception = new String[]{""};
434       }
435 
436         LoggingEvent loggingEvent = new LoggingEvent(null,
437                 logger, timeStamp, levelImpl, message,
438                 threadName,
439                 new ThrowableInformation(exception),
440                 ndc,
441                 info,
442                 properties);
443 
444 
445       events.add(loggingEvent);
446 
447       message = null;
448       ndc = null;
449       exception = null;
450       className = null;
451       methodName = null;
452       fileName = null;
453       lineNumber = null;
454       properties = null;
455     }
456 
457     return events;
458   }
459 
460     /***
461      * Get contents of CDATASection.
462      * @param n CDATASection
463      * @return text content of all text or CDATA children of node.
464      */
465   private String getCData(final Node n) {
466     StringBuffer buf = new StringBuffer();
467     NodeList nl = n.getChildNodes();
468 
469     for (int x = 0; x < nl.getLength(); x++) {
470       Node innerNode = nl.item(x);
471 
472       if (
473         (innerNode.getNodeType() == Node.TEXT_NODE)
474           || (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) {
475         buf.append(innerNode.getNodeValue());
476       }
477     }
478 
479     return buf.toString();
480   }
481 }