001: /*
002: * Copyright 2000-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: ListingErrorHandler.java,v 1.9 2005/01/24 04:04:40 mcnamara Exp $
018: */
019:
020: package org.apache.xml.utils;
021:
022: import java.io.BufferedReader;
023: import java.io.InputStream;
024: import java.io.InputStreamReader;
025: import java.io.PrintWriter;
026: import java.net.URL;
027: import java.net.URLConnection;
028:
029: import javax.xml.transform.ErrorListener;
030: import javax.xml.transform.SourceLocator;
031: import javax.xml.transform.TransformerException;
032:
033: import org.apache.xml.res.XMLErrorResources;
034: import org.apache.xml.res.XMLMessages;
035:
036: import org.xml.sax.ErrorHandler;
037: import org.xml.sax.SAXException;
038: import org.xml.sax.SAXParseException;
039:
040: /**
041: * Sample implementation of similar SAX ErrorHandler and JAXP ErrorListener.
042: *
043: * <p>This implementation is suitable for various use cases, and
044: * provides some basic configuration API's as well to control
045: * when we re-throw errors, etc.</p>
046: *
047: * @author shane_curcuru@us.ibm.com
048: * @version $Id: ListingErrorHandler.java,v 1.9 2005/01/24 04:04:40 mcnamara Exp $
049: * @xsl.usage general
050: */
051: public class ListingErrorHandler implements ErrorHandler, ErrorListener {
052: protected PrintWriter m_pw = null;
053:
054: /**
055: * Constructor ListingErrorHandler; user-supplied PrintWriter.
056: */
057: public ListingErrorHandler(PrintWriter pw) {
058: if (null == pw)
059: throw new NullPointerException(
060: XMLMessages
061: .createXMLMessage(
062: XMLErrorResources.ER_ERRORHANDLER_CREATED_WITH_NULL_PRINTWRITER,
063: null));
064: // "ListingErrorHandler created with null PrintWriter!");
065:
066: m_pw = pw;
067: }
068:
069: /**
070: * Constructor ListingErrorHandler; uses System.err.
071: */
072: public ListingErrorHandler() {
073: m_pw = new PrintWriter(System.err, true);
074: }
075:
076: /* ======== Implement org.xml.sax.ErrorHandler ======== */
077: /**
078: * Receive notification of a warning.
079: *
080: * <p>SAX parsers will use this method to report conditions that
081: * are not errors or fatal errors as defined by the XML 1.0
082: * recommendation. The default behaviour is to take no action.</p>
083: *
084: * <p>The SAX parser must continue to provide normal parsing events
085: * after invoking this method: it should still be possible for the
086: * application to process the document through to the end.</p>
087: *
088: * <p>Filters may use this method to report other, non-XML warnings
089: * as well.</p>
090: *
091: * @param exception The warning information encapsulated in a
092: * SAX parse exception.
093: * @exception org.xml.sax.SAXException Any SAX exception, possibly
094: * wrapping another exception; only if setThrowOnWarning is true.
095: * @see org.xml.sax.SAXParseException
096: */
097: public void warning(SAXParseException exception)
098: throws SAXException {
099: logExceptionLocation(m_pw, exception);
100: // Note: should we really call .toString() below, since
101: // sometimes the message is not properly set?
102: m_pw.println("warning: " + exception.getMessage());
103: m_pw.flush();
104:
105: if (getThrowOnWarning())
106: throw exception;
107: }
108:
109: /**
110: * Receive notification of a recoverable error.
111: *
112: * <p>This corresponds to the definition of "error" in section 1.2
113: * of the W3C XML 1.0 Recommendation. For example, a validating
114: * parser would use this callback to report the violation of a
115: * validity constraint. The default behaviour is to take no
116: * action.</p>
117: *
118: * <p>The SAX parser must continue to provide normal parsing events
119: * after invoking this method: it should still be possible for the
120: * application to process the document through to the end. If the
121: * application cannot do so, then the parser should report a fatal
122: * error even if the XML 1.0 recommendation does not require it to
123: * do so.</p>
124: *
125: * <p>Filters may use this method to report other, non-XML errors
126: * as well.</p>
127: *
128: * @param exception The error information encapsulated in a
129: * SAX parse exception.
130: * @exception org.xml.sax.SAXException Any SAX exception, possibly
131: * wrapping another exception; only if setThrowOnErroris true.
132: * @see org.xml.sax.SAXParseException
133: */
134: public void error(SAXParseException exception) throws SAXException {
135: logExceptionLocation(m_pw, exception);
136: m_pw.println("error: " + exception.getMessage());
137: m_pw.flush();
138:
139: if (getThrowOnError())
140: throw exception;
141: }
142:
143: /**
144: * Receive notification of a non-recoverable error.
145: *
146: * <p>This corresponds to the definition of "fatal error" in
147: * section 1.2 of the W3C XML 1.0 Recommendation. For example, a
148: * parser would use this callback to report the violation of a
149: * well-formedness constraint.</p>
150: *
151: * <p>The application must assume that the document is unusable
152: * after the parser has invoked this method, and should continue
153: * (if at all) only for the sake of collecting addition error
154: * messages: in fact, SAX parsers are free to stop reporting any
155: * other events once this method has been invoked.</p>
156: *
157: * @param exception The error information encapsulated in a
158: * SAX parse exception.
159: * @exception org.xml.sax.SAXException Any SAX exception, possibly
160: * wrapping another exception; only if setThrowOnFatalError is true.
161: * @see org.xml.sax.SAXParseException
162: */
163: public void fatalError(SAXParseException exception)
164: throws SAXException {
165: logExceptionLocation(m_pw, exception);
166: m_pw.println("fatalError: " + exception.getMessage());
167: m_pw.flush();
168:
169: if (getThrowOnFatalError())
170: throw exception;
171: }
172:
173: /* ======== Implement javax.xml.transform.ErrorListener ======== */
174:
175: /**
176: * Receive notification of a warning.
177: *
178: * <p>{@link javax.xml.transform.Transformer} can use this method to report
179: * conditions that are not errors or fatal errors. The default behaviour
180: * is to take no action.</p>
181: *
182: * <p>After invoking this method, the Transformer must continue with
183: * the transformation. It should still be possible for the
184: * application to process the document through to the end.</p>
185: *
186: * @param exception The warning information encapsulated in a
187: * transformer exception.
188: *
189: * @throws javax.xml.transform.TransformerException only if
190: * setThrowOnWarning is true.
191: *
192: * @see javax.xml.transform.TransformerException
193: */
194: public void warning(TransformerException exception)
195: throws TransformerException {
196: logExceptionLocation(m_pw, exception);
197: m_pw.println("warning: " + exception.getMessage());
198: m_pw.flush();
199:
200: if (getThrowOnWarning())
201: throw exception;
202: }
203:
204: /**
205: * Receive notification of a recoverable error.
206: *
207: * <p>The transformer must continue to try and provide normal transformation
208: * after invoking this method. It should still be possible for the
209: * application to process the document through to the end if no other errors
210: * are encountered.</p>
211: *
212: * @param exception The error information encapsulated in a
213: * transformer exception.
214: *
215: * @throws javax.xml.transform.TransformerException only if
216: * setThrowOnError is true.
217: *
218: * @see javax.xml.transform.TransformerException
219: */
220: public void error(TransformerException exception)
221: throws TransformerException {
222: logExceptionLocation(m_pw, exception);
223: m_pw.println("error: " + exception.getMessage());
224: m_pw.flush();
225:
226: if (getThrowOnError())
227: throw exception;
228: }
229:
230: /**
231: * Receive notification of a non-recoverable error.
232: *
233: * <p>The transformer must continue to try and provide normal transformation
234: * after invoking this method. It should still be possible for the
235: * application to process the document through to the end if no other errors
236: * are encountered, but there is no guarantee that the output will be
237: * useable.</p>
238: *
239: * @param exception The error information encapsulated in a
240: * transformer exception.
241: *
242: * @throws javax.xml.transform.TransformerException only if
243: * setThrowOnError is true.
244: *
245: * @see javax.xml.transform.TransformerException
246: */
247: public void fatalError(TransformerException exception)
248: throws TransformerException {
249: logExceptionLocation(m_pw, exception);
250: m_pw.println("error: " + exception.getMessage());
251: m_pw.flush();
252:
253: if (getThrowOnError())
254: throw exception;
255: }
256:
257: /* ======== Implement worker methods ======== */
258:
259: /**
260: * Print out location information about the exception.
261: *
262: * Cribbed from DefaultErrorHandler.printLocation()
263: * @param pw PrintWriter to send output to
264: * @param exception TransformerException or SAXParseException
265: * to log information about
266: */
267: public static void logExceptionLocation(PrintWriter pw,
268: Throwable exception) {
269: if (null == pw)
270: pw = new PrintWriter(System.err, true);
271:
272: SourceLocator locator = null;
273: Throwable cause = exception;
274:
275: // Try to find the locator closest to the cause.
276: do {
277: // Find the current locator, if one present
278: if (cause instanceof SAXParseException) {
279: // A SAXSourceLocator is a Xalan helper class
280: // that implements both a SourceLocator and a SAX Locator
281: //@todo check that the new locator actually has
282: // as much or more information as the
283: // current one already does
284: locator = new SAXSourceLocator(
285: (SAXParseException) cause);
286: } else if (cause instanceof TransformerException) {
287: SourceLocator causeLocator = ((TransformerException) cause)
288: .getLocator();
289: if (null != causeLocator) {
290: locator = causeLocator;
291: }
292: }
293:
294: // Then walk back down the chain of exceptions
295: if (cause instanceof TransformerException)
296: cause = ((TransformerException) cause).getCause();
297: else if (cause instanceof WrappedRuntimeException)
298: cause = ((WrappedRuntimeException) cause)
299: .getException();
300: else if (cause instanceof SAXException)
301: cause = ((SAXException) cause).getException();
302: else
303: cause = null;
304: } while (null != cause);
305:
306: // Formatting note: mimic javac-like errors:
307: // path\filename:123: message-here
308: // systemId:L=1;C=2: message-here
309: if (null != locator) {
310: String id = (locator.getPublicId() != locator.getPublicId()) ? locator
311: .getPublicId()
312: : (null != locator.getSystemId()) ? locator
313: .getSystemId() : "SystemId-Unknown";
314:
315: pw.print(id + ":Line=" + locator.getLineNumber()
316: + ";Column=" + locator.getColumnNumber() + ": ");
317: pw.println("exception:" + exception.getMessage());
318: pw.println("root-cause:"
319: + ((null != cause) ? cause.getMessage() : "null"));
320: logSourceLine(pw, locator);
321: } else {
322: pw.print("SystemId-Unknown:locator-unavailable: ");
323: pw.println("exception:" + exception.getMessage());
324: pw.println("root-cause:"
325: + ((null != cause) ? cause.getMessage() : "null"));
326: }
327: }
328:
329: /**
330: * Print out the specific source line that caused the exception,
331: * if possible to load it.
332: *
333: * @param pw PrintWriter to send output to
334: * @param locator Xalan wrapper for either a JAXP or a SAX
335: * source location object
336: */
337: public static void logSourceLine(PrintWriter pw,
338: SourceLocator locator) {
339: if (null == locator)
340: return;
341:
342: if (null == pw)
343: pw = new PrintWriter(System.err, true);
344:
345: String url = locator.getSystemId();
346: // Bail immediately if we get SystemId-Unknown
347: //@todo future improvement: attempt to get resource
348: // from a publicId if possible
349: if (null == url) {
350: pw.println("line: (No systemId; cannot read file)");
351: pw.println();
352: return;
353: }
354:
355: //@todo attempt to get DOM backpointer or other ids
356:
357: try {
358: int line = locator.getLineNumber();
359: int column = locator.getColumnNumber();
360: pw.println("line: " + getSourceLine(url, line));
361: StringBuffer buf = new StringBuffer("line: ");
362: for (int i = 1; i < column; i++) {
363: buf.append(' ');
364: }
365: buf.append('^');
366: pw.println(buf.toString());
367: } catch (Exception e) {
368: pw.println("line: logSourceLine unavailable due to: "
369: + e.getMessage());
370: pw.println();
371: }
372: }
373:
374: /**
375: * Return the specific source line that caused the exception,
376: * if possible to load it; allow exceptions to be thrown.
377: *
378: * @author shane_curcuru@us.ibm.com
379: */
380: protected static String getSourceLine(String sourceUrl, int lineNum)
381: throws Exception {
382: URL url = null;
383: // Get a URL from the sourceUrl
384: try {
385: // Try to get a URL from it as-is
386: url = new URL(sourceUrl);
387: } catch (java.net.MalformedURLException mue) {
388: int indexOfColon = sourceUrl.indexOf(':');
389: int indexOfSlash = sourceUrl.indexOf('/');
390:
391: if ((indexOfColon != -1) && (indexOfSlash != -1)
392: && (indexOfColon < indexOfSlash)) {
393: // The url is already absolute, but we could not get
394: // the system to form it, so bail
395: throw mue;
396: } else {
397: // The url is relative, so attempt to get absolute
398: url = new URL(SystemIDResolver
399: .getAbsoluteURI(sourceUrl));
400: // If this fails, allow the exception to propagate
401: }
402: }
403:
404: String line = null;
405: InputStream is = null;
406: BufferedReader br = null;
407: try {
408: // Open the URL and read to our specified line
409: URLConnection uc = url.openConnection();
410: is = uc.getInputStream();
411: br = new BufferedReader(new InputStreamReader(is));
412:
413: // Not the most efficient way, but it works
414: // (Feel free to patch to seek to the appropriate line)
415: for (int i = 1; i <= lineNum; i++) {
416: line = br.readLine();
417: }
418:
419: }
420: // Allow exceptions to propagate from here, but ensure
421: // streams are closed!
422: finally {
423: br.close();
424: is.close();
425: }
426:
427: // Return whatever we found
428: return line;
429: }
430:
431: /* ======== Implement settable properties ======== */
432:
433: /**
434: * User-settable behavior: when to re-throw exceptions.
435: *
436: * <p>This allows per-instance configuration of
437: * ListingErrorHandlers. You can ask us to either throw
438: * an exception when we're called for various warning /
439: * error / fatalErrors, or simply log them and continue.</p>
440: *
441: * @param b if we should throw an exception on warnings
442: */
443: public void setThrowOnWarning(boolean b) {
444: throwOnWarning = b;
445: }
446:
447: /**
448: * User-settable behavior: when to re-throw exceptions.
449: *
450: * @return if we throw an exception on warnings
451: */
452: public boolean getThrowOnWarning() {
453: return throwOnWarning;
454: }
455:
456: /** If we should throw exception on warnings; default:false. */
457: protected boolean throwOnWarning = false;
458:
459: /**
460: * User-settable behavior: when to re-throw exceptions.
461: *
462: * <p>This allows per-instance configuration of
463: * ListingErrorHandlers. You can ask us to either throw
464: * an exception when we're called for various warning /
465: * error / fatalErrors, or simply log them and continue.</p>
466: *
467: * <p>Note that the behavior of many parsers/transformers
468: * after an error is not necessarily defined!</p>
469: *
470: * @param b if we should throw an exception on errors
471: */
472: public void setThrowOnError(boolean b) {
473: throwOnError = b;
474: }
475:
476: /**
477: * User-settable behavior: when to re-throw exceptions.
478: *
479: * @return if we throw an exception on errors
480: */
481: public boolean getThrowOnError() {
482: return throwOnError;
483: }
484:
485: /** If we should throw exception on errors; default:true. */
486: protected boolean throwOnError = true;
487:
488: /**
489: * User-settable behavior: when to re-throw exceptions.
490: *
491: * <p>This allows per-instance configuration of
492: * ListingErrorHandlers. You can ask us to either throw
493: * an exception when we're called for various warning /
494: * error / fatalErrors, or simply log them and continue.</p>
495: *
496: * <p>Note that the behavior of many parsers/transformers
497: * after a fatalError is not necessarily defined, most
498: * products will probably barf if you continue.</p>
499: *
500: * @param b if we should throw an exception on fatalErrors
501: */
502: public void setThrowOnFatalError(boolean b) {
503: throwOnFatalError = b;
504: }
505:
506: /**
507: * User-settable behavior: when to re-throw exceptions.
508: *
509: * @return if we throw an exception on fatalErrors
510: */
511: public boolean getThrowOnFatalError() {
512: return throwOnFatalError;
513: }
514:
515: /** If we should throw exception on fatalErrors; default:true. */
516: protected boolean throwOnFatalError = true;
517:
518: }
|