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