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    }