1/*
2 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26
27package java.util.logging;
28
29import java.io.*;
30import java.nio.charset.Charset;
31import java.util.*;
32
33/**
34 * Format a LogRecord into a standard XML format.
35 * <p>
36 * The DTD specification is provided as Appendix A to the
37 * Java Logging APIs specification.
38 * <p>
39 * The XMLFormatter can be used with arbitrary character encodings,
40 * but it is recommended that it normally be used with UTF-8.  The
41 * character encoding can be set on the output Handler.
42 *
43 * @since 1.4
44 */
45
46public class XMLFormatter extends Formatter {
47    private LogManager manager = LogManager.getLogManager();
48
49    // Append a two digit number.
50    private void a2(StringBuilder sb, int x) {
51        if (x < 10) {
52            sb.append('0');
53        }
54        sb.append(x);
55    }
56
57    // Append the time and date in ISO 8601 format
58    private void appendISO8601(StringBuilder sb, long millis) {
59        GregorianCalendar cal = new GregorianCalendar();
60        cal.setTimeInMillis(millis);
61        sb.append(cal.get(Calendar.YEAR));
62        sb.append('-');
63        a2(sb, cal.get(Calendar.MONTH) + 1);
64        sb.append('-');
65        a2(sb, cal.get(Calendar.DAY_OF_MONTH));
66        sb.append('T');
67        a2(sb, cal.get(Calendar.HOUR_OF_DAY));
68        sb.append(':');
69        a2(sb, cal.get(Calendar.MINUTE));
70        sb.append(':');
71        a2(sb, cal.get(Calendar.SECOND));
72    }
73
74    // Append to the given StringBuilder an escaped version of the
75    // given text string where XML special characters have been escaped.
76    // For a null string we append "<null>"
77    private void escape(StringBuilder sb, String text) {
78        if (text == null) {
79            text = "<null>";
80        }
81        for (int i = 0; i < text.length(); i++) {
82            char ch = text.charAt(i);
83            if (ch == '<') {
84                sb.append("&lt;");
85            } else if (ch == '>') {
86                sb.append("&gt;");
87            } else if (ch == '&') {
88                sb.append("&amp;");
89            } else {
90                sb.append(ch);
91            }
92        }
93    }
94
95    /**
96     * Format the given message to XML.
97     * <p>
98     * This method can be overridden in a subclass.
99     * It is recommended to use the {@link Formatter#formatMessage}
100     * convenience method to localize and format the message field.
101     *
102     * @param record the log record to be formatted.
103     * @return a formatted log record
104     */
105    public String format(LogRecord record) {
106        StringBuilder sb = new StringBuilder(500);
107        sb.append("<record>\n");
108
109        sb.append("  <date>");
110        appendISO8601(sb, record.getMillis());
111        sb.append("</date>\n");
112
113        sb.append("  <millis>");
114        sb.append(record.getMillis());
115        sb.append("</millis>\n");
116
117        sb.append("  <sequence>");
118        sb.append(record.getSequenceNumber());
119        sb.append("</sequence>\n");
120
121        String name = record.getLoggerName();
122        if (name != null) {
123            sb.append("  <logger>");
124            escape(sb, name);
125            sb.append("</logger>\n");
126        }
127
128        sb.append("  <level>");
129        escape(sb, record.getLevel().toString());
130        sb.append("</level>\n");
131
132        if (record.getSourceClassName() != null) {
133            sb.append("  <class>");
134            escape(sb, record.getSourceClassName());
135            sb.append("</class>\n");
136        }
137
138        if (record.getSourceMethodName() != null) {
139            sb.append("  <method>");
140            escape(sb, record.getSourceMethodName());
141            sb.append("</method>\n");
142        }
143
144        sb.append("  <thread>");
145        sb.append(record.getThreadID());
146        sb.append("</thread>\n");
147
148        if (record.getMessage() != null) {
149            // Format the message string and its accompanying parameters.
150            String message = formatMessage(record);
151            sb.append("  <message>");
152            escape(sb, message);
153            sb.append("</message>");
154            sb.append("\n");
155        } else {
156            sb.append("<message/>");
157            sb.append("\n");
158        }
159
160        // If the message is being localized, output the key, resource
161        // bundle name, and params.
162        ResourceBundle bundle = record.getResourceBundle();
163        try {
164            if (bundle != null && bundle.getString(record.getMessage()) != null) {
165                sb.append("  <key>");
166                escape(sb, record.getMessage());
167                sb.append("</key>\n");
168                sb.append("  <catalog>");
169                escape(sb, record.getResourceBundleName());
170                sb.append("</catalog>\n");
171            }
172        } catch (Exception ex) {
173            // The message is not in the catalog.  Drop through.
174        }
175
176        Object parameters[] = record.getParameters();
177        //  Check to see if the parameter was not a messagetext format
178        //  or was not null or empty
179        if ( parameters != null && parameters.length != 0
180                && record.getMessage().indexOf("{") == -1 ) {
181            for (int i = 0; i < parameters.length; i++) {
182                sb.append("  <param>");
183                try {
184                    escape(sb, parameters[i].toString());
185                } catch (Exception ex) {
186                    sb.append("???");
187                }
188                sb.append("</param>\n");
189            }
190        }
191
192        if (record.getThrown() != null) {
193            // Report on the state of the throwable.
194            Throwable th = record.getThrown();
195            sb.append("  <exception>\n");
196            sb.append("    <message>");
197            escape(sb, th.toString());
198            sb.append("</message>\n");
199            StackTraceElement trace[] = th.getStackTrace();
200            for (int i = 0; i < trace.length; i++) {
201                StackTraceElement frame = trace[i];
202                sb.append("    <frame>\n");
203                sb.append("      <class>");
204                escape(sb, frame.getClassName());
205                sb.append("</class>\n");
206                sb.append("      <method>");
207                escape(sb, frame.getMethodName());
208                sb.append("</method>\n");
209                // Check for a line number.
210                if (frame.getLineNumber() >= 0) {
211                    sb.append("      <line>");
212                    sb.append(frame.getLineNumber());
213                    sb.append("</line>\n");
214                }
215                sb.append("    </frame>\n");
216            }
217            sb.append("  </exception>\n");
218        }
219
220        sb.append("</record>\n");
221        return sb.toString();
222    }
223
224    /**
225     * Return the header string for a set of XML formatted records.
226     *
227     * @param   h  The target handler (can be null)
228     * @return  a valid XML string
229     */
230    public String getHead(Handler h) {
231        StringBuilder sb = new StringBuilder();
232        String encoding;
233        sb.append("<?xml version=\"1.0\"");
234
235        if (h != null) {
236            encoding = h.getEncoding();
237        } else {
238            encoding = null;
239        }
240
241        if (encoding == null) {
242            // Figure out the default encoding.
243            encoding = java.nio.charset.Charset.defaultCharset().name();
244        }
245        // Try to map the encoding name to a canonical name.
246        try {
247            Charset cs = Charset.forName(encoding);
248            encoding = cs.name();
249        } catch (Exception ex) {
250            // We hit problems finding a canonical name.
251            // Just use the raw encoding name.
252        }
253
254        sb.append(" encoding=\"");
255        sb.append(encoding);
256        sb.append("\"");
257        sb.append(" standalone=\"no\"?>\n");
258        sb.append("<!DOCTYPE log SYSTEM \"logger.dtd\">\n");
259        sb.append("<log>\n");
260        return sb.toString();
261    }
262
263    /**
264     * Return the tail string for a set of XML formatted records.
265     *
266     * @param   h  The target handler (can be null)
267     * @return  a valid XML string
268     */
269    public String getTail(Handler h) {
270        return "</log>\n";
271    }
272}
273