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 }