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 }