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