View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.struts2.jasper.compiler;
18  
19  import org.apache.struts2.jasper.JasperException;
20  import org.apache.struts2.jasper.JspCompilationContext;
21  import org.xml.sax.SAXException;
22  
23  import java.io.BufferedReader;
24  import java.io.IOException;
25  import java.io.StringReader;
26  import java.net.MalformedURLException;
27  import java.util.Vector;
28  
29  /***
30   * Class responsible for dispatching JSP parse and javac compilation errors
31   * to the configured error handler.
32   * <p/>
33   * This class is also responsible for localizing any error codes before they
34   * are passed on to the configured error handler.
35   * <p/>
36   * In the case of a Java compilation error, the compiler error message is
37   * parsed into an array of JavacErrorDetail instances, which is passed on to
38   * the configured error handler.
39   *
40   * @author Jan Luehe
41   * @author Kin-man Chung
42   */
43  public class ErrorDispatcher {
44  
45      // Custom error handler
46      private ErrorHandler errHandler;
47  
48      // Indicates whether the compilation was initiated by JspServlet or JspC
49      private boolean jspcMode = false;
50  
51  
52      /*
53       * Constructor.
54       *
55       * @param jspcMode true if compilation has been initiated by JspC, false
56       * otherwise
57       */
58      public ErrorDispatcher(boolean jspcMode) {
59          // XXX check web.xml for custom error handler
60          errHandler = new DefaultErrorHandler();
61          this.jspcMode = jspcMode;
62      }
63  
64      /*
65       * Dispatches the given JSP parse error to the configured error handler.
66       *
67       * The given error code is localized. If it is not found in the
68       * resource bundle for localized error messages, it is used as the error
69       * message.
70       *
71       * @param errCode Error code
72       */
73      public void jspError(String errCode) throws JasperException {
74          dispatch(null, errCode, null, null);
75      }
76  
77      /*
78       * Dispatches the given JSP parse error to the configured error handler.
79       *
80       * The given error code is localized. If it is not found in the
81       * resource bundle for localized error messages, it is used as the error
82       * message.
83       *
84       * @param where Error location
85       * @param errCode Error code
86       */
87      public void jspError(Mark where, String errCode) throws JasperException {
88          dispatch(where, errCode, null, null);
89      }
90  
91      /*
92       * Dispatches the given JSP parse error to the configured error handler.
93       *
94       * The given error code is localized. If it is not found in the
95       * resource bundle for localized error messages, it is used as the error
96       * message.
97       *
98       * @param n Node that caused the error
99       * @param errCode Error code
100      */
101     public void jspError(Node n, String errCode) throws JasperException {
102         dispatch(n.getStart(), errCode, null, null);
103     }
104 
105     /*
106      * Dispatches the given JSP parse error to the configured error handler.
107      *
108      * The given error code is localized. If it is not found in the
109      * resource bundle for localized error messages, it is used as the error
110      * message.
111      *
112      * @param errCode Error code
113      * @param arg Argument for parametric replacement
114      */
115     public void jspError(String errCode, String arg) throws JasperException {
116         dispatch(null, errCode, new Object[]{arg}, null);
117     }
118 
119     /*
120      * Dispatches the given JSP parse error to the configured error handler.
121      *
122      * The given error code is localized. If it is not found in the
123      * resource bundle for localized error messages, it is used as the error
124      * message.
125      *
126      * @param where Error location
127      * @param errCode Error code
128      * @param arg Argument for parametric replacement
129      */
130     public void jspError(Mark where, String errCode, String arg)
131             throws JasperException {
132         dispatch(where, errCode, new Object[]{arg}, null);
133     }
134 
135     /*
136      * Dispatches the given JSP parse error to the configured error handler.
137      *
138      * The given error code is localized. If it is not found in the
139      * resource bundle for localized error messages, it is used as the error
140      * message.
141      *
142      * @param n Node that caused the error
143      * @param errCode Error code
144      * @param arg Argument for parametric replacement
145      */
146     public void jspError(Node n, String errCode, String arg)
147             throws JasperException {
148         dispatch(n.getStart(), errCode, new Object[]{arg}, null);
149     }
150 
151     /*
152      * Dispatches the given JSP parse error to the configured error handler.
153      *
154      * The given error code is localized. If it is not found in the
155      * resource bundle for localized error messages, it is used as the error
156      * message.
157      *
158      * @param errCode Error code
159      * @param arg1 First argument for parametric replacement
160      * @param arg2 Second argument for parametric replacement
161      */
162     public void jspError(String errCode, String arg1, String arg2)
163             throws JasperException {
164         dispatch(null, errCode, new Object[]{arg1, arg2}, null);
165     }
166 
167     /*
168      * Dispatches the given JSP parse error to the configured error handler.
169      *
170      * The given error code is localized. If it is not found in the
171      * resource bundle for localized error messages, it is used as the error
172      * message.
173      *
174      * @param errCode Error code
175      * @param arg1 First argument for parametric replacement
176      * @param arg2 Second argument for parametric replacement
177      * @param arg3 Third argument for parametric replacement
178      */
179     public void jspError(String errCode, String arg1, String arg2, String arg3)
180             throws JasperException {
181         dispatch(null, errCode, new Object[]{arg1, arg2, arg3}, null);
182     }
183 
184     /*
185      * Dispatches the given JSP parse error to the configured error handler.
186      *
187      * The given error code is localized. If it is not found in the
188      * resource bundle for localized error messages, it is used as the error
189      * message.
190      *
191      * @param where Error location
192      * @param errCode Error code
193      * @param arg1 First argument for parametric replacement
194      * @param arg2 Second argument for parametric replacement
195      */
196     public void jspError(Mark where, String errCode, String arg1, String arg2)
197             throws JasperException {
198         dispatch(where, errCode, new Object[]{arg1, arg2}, null);
199     }
200 
201     /*
202      * Dispatches the given JSP parse error to the configured error handler.
203      *
204      * The given error code is localized. If it is not found in the
205      * resource bundle for localized error messages, it is used as the error
206      * message.
207      *
208      * @param where Error location
209      * @param errCode Error code
210      * @param arg1 First argument for parametric replacement
211      * @param arg2 Second argument for parametric replacement
212      * @param arg3 Third argument for parametric replacement
213      */
214 
215     public void jspError(Mark where, String errCode, String arg1, String arg2,
216                          String arg3)
217             throws JasperException {
218         dispatch(where, errCode, new Object[]{arg1, arg2, arg3}, null);
219     }
220 
221     /*
222      * Dispatches the given JSP parse error to the configured error handler.
223      *
224      * The given error code is localized. If it is not found in the
225      * resource bundle for localized error messages, it is used as the error
226      * message.
227      *
228      * @param n Node that caused the error
229      * @param errCode Error code
230      * @param arg1 First argument for parametric replacement
231      * @param arg2 Second argument for parametric replacement
232      */
233 
234     public void jspError(Node n, String errCode, String arg1, String arg2)
235             throws JasperException {
236         dispatch(n.getStart(), errCode, new Object[]{arg1, arg2}, null);
237     }
238 
239     /*
240      * Dispatches the given JSP parse error to the configured error handler.
241      *
242      * The given error code is localized. If it is not found in the
243      * resource bundle for localized error messages, it is used as the error
244      * message.
245      *
246      * @param n Node that caused the error
247      * @param errCode Error code
248      * @param arg1 First argument for parametric replacement
249      * @param arg2 Second argument for parametric replacement
250      * @param arg3 Third argument for parametric replacement
251      */
252 
253     public void jspError(Node n, String errCode, String arg1, String arg2,
254                          String arg3)
255             throws JasperException {
256         dispatch(n.getStart(), errCode, new Object[]{arg1, arg2, arg3}, null);
257     }
258 
259     /*
260      * Dispatches the given parsing exception to the configured error handler.
261      *
262      * @param e Parsing exception
263      */
264     public void jspError(Exception e) throws JasperException {
265         dispatch(null, null, null, e);
266     }
267 
268     /*
269      * Dispatches the given JSP parse error to the configured error handler.
270      *
271      * The given error code is localized. If it is not found in the
272      * resource bundle for localized error messages, it is used as the error
273      * message.
274      *
275      * @param errCode Error code
276      * @param arg Argument for parametric replacement
277      * @param e Parsing exception
278      */
279     public void jspError(String errCode, String arg, Exception e)
280             throws JasperException {
281         dispatch(null, errCode, new Object[]{arg}, e);
282     }
283 
284     /*
285      * Dispatches the given JSP parse error to the configured error handler.
286      *
287      * The given error code is localized. If it is not found in the
288      * resource bundle for localized error messages, it is used as the error
289      * message.
290      *
291      * @param n Node that caused the error
292      * @param errCode Error code
293      * @param arg Argument for parametric replacement
294      * @param e Parsing exception
295      */
296     public void jspError(Node n, String errCode, String arg, Exception e)
297             throws JasperException {
298         dispatch(n.getStart(), errCode, new Object[]{arg}, e);
299     }
300 
301     /***
302      * Parses the given error message into an array of javac compilation error
303      * messages (one per javac compilation error line number).
304      *
305      * @param errMsg Error message
306      * @param fname  Name of Java source file whose compilation failed
307      * @param page   Node representation of JSP page from which the Java source
308      *               file was generated
309      * @return Array of javac compilation errors, or null if the given error
310      *         message does not contain any compilation error line numbers
311      */
312     public static JavacErrorDetail[] parseJavacErrors(String errMsg,
313                                                       String fname,
314                                                       Node.Nodes page)
315             throws JasperException, IOException {
316 
317         return parseJavacMessage(errMsg, fname, page);
318     }
319 
320     /*
321      * Dispatches the given javac compilation errors to the configured error
322      * handler.
323      *
324      * @param javacErrors Array of javac compilation errors
325      */
326     public void javacError(JavacErrorDetail[] javacErrors)
327             throws JasperException {
328 
329         errHandler.javacError(javacErrors);
330     }
331 
332 
333     /*
334      * Dispatches the given compilation error report and exception to the
335      * configured error handler.
336      *
337      * @param errorReport Compilation error report
338      * @param e Compilation exception
339      */
340     public void javacError(String errorReport, Exception e)
341             throws JasperException {
342 
343         errHandler.javacError(errorReport, e);
344     }
345 
346 
347     //**********************************************************************
348     // Private utility methods
349 
350     /*
351      * Dispatches the given JSP parse error to the configured error handler.
352      *
353      * The given error code is localized. If it is not found in the
354      * resource bundle for localized error messages, it is used as the error
355      * message.
356      *
357      * @param where Error location
358      * @param errCode Error code
359      * @param args Arguments for parametric replacement
360      * @param e Parsing exception
361      */
362 
363     private void dispatch(Mark where, String errCode, Object[] args,
364                           Exception e) throws JasperException {
365         String file = null;
366         String errMsg = null;
367         int line = -1;
368         int column = -1;
369         boolean hasLocation = false;
370 
371         // Localize
372         if (errCode != null) {
373             errMsg = Localizer.getMessage(errCode, args);
374         } else if (e != null) {
375             // give a hint about what's wrong
376             errMsg = e.getMessage();
377         }
378 
379         // Get error location
380         if (where != null) {
381             if (jspcMode) {
382                 // Get the full URL of the resource that caused the error
383                 try {
384                     file = where.getURL().toString();
385                 } catch (MalformedURLException me) {
386                     // Fallback to using context-relative path
387                     file = where.getFile();
388                 }
389             } else {
390                 // Get the context-relative resource path, so as to not
391                 // disclose any local filesystem details
392                 file = where.getFile();
393             }
394             line = where.getLineNumber();
395             column = where.getColumnNumber();
396             hasLocation = true;
397         }
398 
399         // Get nested exception
400         Exception nestedEx = e;
401         if ((e instanceof SAXException)
402                 && (((SAXException) e).getException() != null)) {
403             nestedEx = ((SAXException) e).getException();
404         }
405 
406         if (hasLocation) {
407             errHandler.jspError(file, line, column, errMsg, nestedEx);
408         } else {
409             errHandler.jspError(errMsg, nestedEx);
410         }
411     }
412 
413     /*
414      * Parses the given Java compilation error message, which may contain one
415      * or more compilation errors, into an array of JavacErrorDetail instances.
416      *
417      * Each JavacErrorDetail instance contains the information about a single
418      * compilation error.
419      *
420      * @param errMsg Compilation error message that was generated by the
421      * javac compiler
422      * @param fname Name of Java source file whose compilation failed
423      * @param page Node representation of JSP page from which the Java source
424      * file was generated
425      *
426      * @return Array of JavacErrorDetail instances corresponding to the
427      * compilation errors
428      */
429     private static JavacErrorDetail[] parseJavacMessage(
430             String errMsg, String fname, Node.Nodes page)
431             throws IOException, JasperException {
432 
433         Vector errVec = new Vector();
434         StringBuffer errMsgBuf = null;
435         int lineNum = -1;
436         JavacErrorDetail javacError = null;
437 
438         BufferedReader reader = new BufferedReader(new StringReader(errMsg));
439 
440         /*
441         * Parse compilation errors. Each compilation error consists of a file
442         * path and error line number, followed by a number of lines describing
443         * the error.
444         */
445         String line = null;
446         while ((line = reader.readLine()) != null) {
447 
448             /*
449             * Error line number is delimited by set of colons.
450             * Ignore colon following drive letter on Windows (fromIndex = 2).
451             * XXX Handle deprecation warnings that don't have line info
452             */
453             int beginColon = line.indexOf(':', 2);
454             int endColon = line.indexOf(':', beginColon + 1);
455             if ((beginColon >= 0) && (endColon >= 0)) {
456                 if (javacError != null) {
457                     // add previous error to error vector
458                     errVec.add(javacError);
459                 }
460 
461                 String lineNumStr = line.substring(beginColon + 1, endColon);
462                 try {
463                     lineNum = Integer.parseInt(lineNumStr);
464                 } catch (NumberFormatException e) {
465                     // XXX
466                 }
467 
468                 errMsgBuf = new StringBuffer();
469 
470                 javacError = createJavacError(fname, page, errMsgBuf, lineNum);
471             }
472 
473             // Ignore messages preceding first error
474             if (errMsgBuf != null) {
475                 errMsgBuf.append(line);
476                 errMsgBuf.append("\n");
477             }
478         }
479 
480         // Add last error to error vector
481         if (javacError != null) {
482             errVec.add(javacError);
483         }
484 
485         reader.close();
486 
487         JavacErrorDetail[] errDetails = null;
488         if (errVec.size() > 0) {
489             errDetails = new JavacErrorDetail[errVec.size()];
490             errVec.copyInto(errDetails);
491         }
492 
493         return errDetails;
494     }
495 
496 
497     /***
498      * @param fname
499      * @param page
500      * @param errMsgBuf
501      * @param lineNum
502      * @return JavacErrorDetail The error details
503      * @throws JasperException
504      */
505     public static JavacErrorDetail createJavacError(String fname,
506                                                     Node.Nodes page,
507                                                     StringBuffer errMsgBuf, int lineNum) throws JasperException {
508         return createJavacError(fname, page, errMsgBuf, lineNum, null);
509     }
510 
511     /***
512      * @param fname
513      * @param page
514      * @param errMsgBuf
515      * @param lineNum
516      * @param ctxt
517      * @return JavacErrorDetail The error details
518      * @throws JasperException
519      */
520     public static JavacErrorDetail createJavacError(String fname,
521                                                     Node.Nodes page,
522                                                     StringBuffer errMsgBuf, int lineNum, JspCompilationContext ctxt)
523             throws JasperException {
524         JavacErrorDetail javacError;
525         // Attempt to map javac error line number to line in JSP page
526         ErrorVisitor errVisitor = new ErrorVisitor(lineNum);
527         page.visit(errVisitor);
528         Node errNode = errVisitor.getJspSourceNode();
529         if ((errNode != null) && (errNode.getStart() != null)) {
530             // If this is a scriplet node then there is a one to one mapping
531             // between JSP lines and Java lines
532             if (errVisitor.getJspSourceNode() instanceof Node.Scriptlet) {
533                 javacError = new JavacErrorDetail(
534                         fname,
535                         lineNum,
536                         errNode.getStart().getFile(),
537                         errNode.getStart().getLineNumber() + lineNum -
538                                 errVisitor.getJspSourceNode().getBeginJavaLine(),
539                         errMsgBuf,
540                         ctxt);
541             } else {
542                 javacError = new JavacErrorDetail(
543                         fname,
544                         lineNum,
545                         errNode.getStart().getFile(),
546                         errNode.getStart().getLineNumber(),
547                         errMsgBuf,
548                         ctxt);
549             }
550         } else {
551             /*
552              * javac error line number cannot be mapped to JSP page
553              * line number. For example, this is the case if a 
554              * scriptlet is missing a closing brace, which causes
555              * havoc with the try-catch-finally block that the code
556              * generator places around all generated code: As a result
557              * of this, the javac error line numbers will be outside
558              * the range of begin and end java line numbers that were
559              * generated for the scriptlet, and therefore cannot be
560              * mapped to the start line number of the scriptlet in the
561              * JSP page.
562              * Include just the javac error info in the error detail.
563              */
564             javacError = new JavacErrorDetail(
565                     fname,
566                     lineNum,
567                     errMsgBuf);
568         }
569         return javacError;
570     }
571 
572 
573     /*
574      * Visitor responsible for mapping a line number in the generated servlet
575      * source code to the corresponding JSP node.
576      */
577     static class ErrorVisitor extends Node.Visitor {
578 
579         // Java source line number to be mapped
580         private int lineNum;
581 
582         /*
583        * JSP node whose Java source code range in the generated servlet
584        * contains the Java source line number to be mapped
585        */
586         Node found;
587 
588         /*
589        * Constructor.
590        *
591        * @param lineNum Source line number in the generated servlet code
592        */
593         public ErrorVisitor(int lineNum) {
594             this.lineNum = lineNum;
595         }
596 
597         public void doVisit(Node n) throws JasperException {
598             if ((lineNum >= n.getBeginJavaLine())
599                     && (lineNum < n.getEndJavaLine())) {
600                 found = n;
601             }
602         }
603 
604         /*
605        * Gets the JSP node to which the source line number in the generated
606        * servlet code was mapped.
607        *
608        * @return JSP node to which the source line number in the generated
609        * servlet code was mapped
610        */
611         public Node getJspSourceNode() {
612             return found;
613         }
614     }
615 }