2 * Copyright 2004-2005 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org
.codehaus
.groovy
.grails
.web
.errors
;
18 import org
.apache
.commons
.lang
.StringUtils
;
19 import org
.apache
.commons
.logging
.Log
;
20 import org
.apache
.commons
.logging
.LogFactory
;
21 import org
.codehaus
.groovy
.control
.MultipleCompilationErrorsException
;
22 import org
.codehaus
.groovy
.control
.messages
.SyntaxErrorMessage
;
23 import org
.codehaus
.groovy
.grails
.commons
.*;
24 import org
.codehaus
.groovy
.grails
.exceptions
.GrailsException
;
25 import org
.codehaus
.groovy
.grails
.exceptions
.SourceCodeAware
;
26 import org
.codehaus
.groovy
.grails
.web
.pages
.GroovyPagesTemplateEngine
;
27 import org
.codehaus
.groovy
.grails
.web
.servlet
.DefaultGrailsApplicationAttributes
;
28 import org
.codehaus
.groovy
.grails
.web
.servlet
.GrailsApplicationAttributes
;
29 import org
.springframework
.core
.io
.Resource
;
30 import org
.springframework
.core
.io
.support
.PathMatchingResourcePatternResolver
;
32 import javax
.servlet
.ServletContext
;
34 import java
.util
.regex
.Matcher
;
35 import java
.util
.regex
.Pattern
;
38 * An exception that wraps a Grails RuntimeException and attempts to extract more relevent diagnostic messages from the stack trace
40 * @author Graeme Rocher
43 * Created: 22 Dec, 2005
45 public class GrailsWrappedRuntimeException
extends GrailsException
{
47 private static final Pattern PARSE_DETAILS_STEP1
= Pattern
.compile("\\((\\w+)\\.groovy:(\\d+)\\)");
48 private static final Pattern PARSE_DETAILS_STEP2
= Pattern
.compile("at\\s{1}(\\w+)\\$_closure\\d+\\.doCall\\(\\1:(\\d+)\\)");
49 private static final Pattern PARSE_DETAILS_STEP3
= Pattern
.compile("\\p{Upper}(\\S+?)\\$_closure\\d+\\.doCall\\(\\1:(\\d+)\\)");
50 private static final Pattern PARSE_GSP_DETAILS_STEP1
= Pattern
.compile("(\\S+?)_\\S+?_gsp.run\\((\\S+?\\.gsp):(\\d+)\\)");
51 public static final String URL_PREFIX
= "/WEB-INF/grails-app/";
52 private static final Log LOG
= LogFactory
.getLog(GrailsWrappedRuntimeException
.class);
53 private String className
= UNKNOWN
;
54 private int lineNumber
= - 1;
55 private String stackTrace
;
56 private String
[] codeSnippet
= new String
[0];
57 private String gspFile
;
58 private Throwable cause
;
59 private PathMatchingResourcePatternResolver resolver
= new PathMatchingResourcePatternResolver();
60 private String
[] stackTraceLines
;
61 private static final String UNKNOWN
= "Unknown";
65 * @param servletContext The ServletContext instance
66 * @param t The exception that was thrown
68 public GrailsWrappedRuntimeException(ServletContext servletContext
, Throwable t
) {
69 super(t
.getMessage(), t
);
71 StringWriter sw
= new StringWriter();
72 PrintWriter pw
= new PrintWriter(sw
);
73 cause
.printStackTrace(pw
);
74 this.stackTrace
= sw
.toString();
76 while(t
.getCause()!=cause
) {
77 if(t
.getCause() == null) {
84 this.stackTraceLines
= this.stackTrace
.split("\\n");
86 if(cause
instanceof MultipleCompilationErrorsException
) {
87 MultipleCompilationErrorsException mcee
= (MultipleCompilationErrorsException
)cause
;
88 Object message
= mcee
.getErrorCollector().getErrors().iterator().next();
89 if(message
instanceof SyntaxErrorMessage
) {
90 SyntaxErrorMessage sem
= (SyntaxErrorMessage
)message
;
91 this.lineNumber
= sem
.getCause().getLine();
93 String messageText
= sw
.toString();
94 if(messageText
.indexOf(':') > -1) {
95 this.className
= sw
.toString().substring(0,messageText
.indexOf(':'));
96 this.className
= this.className
.trim();
103 Matcher m1
= PARSE_DETAILS_STEP1
.matcher(stackTrace
);
104 Matcher m2
= PARSE_DETAILS_STEP2
.matcher(stackTrace
);
105 Matcher gsp
= PARSE_GSP_DETAILS_STEP1
.matcher(stackTrace
);
108 this.className
= gsp
.group(2);
109 this.lineNumber
= Integer
.parseInt(gsp
.group(3));
110 this.gspFile
= URL_PREFIX
+ "views/" + gsp
.group(1) + '/' + this.className
;
114 this.className
= m1
.group(1);
115 this.lineNumber
= Integer
.parseInt(m1
.group(2));
118 this.className
= m2
.group(1);
119 this.lineNumber
= Integer
.parseInt(m2
.group(2));
123 catch(NumberFormatException nfex
) {
128 LineNumberReader reader
= null;
130 if(cause
instanceof SourceCodeAware
&& className
.equals(UNKNOWN
)) {
131 className
= ((SourceCodeAware
)cause
).getFileName();
134 if(getLineNumber() > -1) {
136 String fileName
= this.className
.replace('.', '/') + ".groovy";
137 String urlPrefix
= "";
138 if(gspFile
== null) {
141 GrailsApplication application
= ApplicationHolder
.getApplication();
142 // @todo Refactor this to get the urlPrefix from the ArtefactHandler
143 if(application
.isArtefactOfType(ControllerArtefactHandler
.TYPE
, className
)) {
144 urlPrefix
+= "/controllers/";
146 else if(application
.isArtefactOfType(TagLibArtefactHandler
.TYPE
, className
)) {
147 urlPrefix
+= "/taglib/";
149 else if(application
.isArtefactOfType(ServiceArtefactHandler
.TYPE
, className
)) {
150 urlPrefix
+= "/services/";
152 url
= URL_PREFIX
+ urlPrefix
+ fileName
;
156 GrailsApplicationAttributes attrs
= new DefaultGrailsApplicationAttributes(servletContext
);
157 GroovyPagesTemplateEngine engine
= attrs
.getPagesTemplateEngine();
158 int[] lineNumbers
= engine
.calculateLineNumbersForPage(servletContext
,url
);
159 if(this.lineNumber
< lineNumbers
.length
) {
160 this.lineNumber
= lineNumbers
[this.lineNumber
- 1];
165 InputStream in
= null;
166 if(!StringUtils
.isBlank(url
)) {
167 in
= servletContext
.getResourceAsStream(url
);
168 LOG
.debug("Attempting to display code snippet found in url " + url
);
171 Resource r
= resolver
.getResource("grails-app" + urlPrefix
+ fileName
);
172 in
= r
.getInputStream();
173 } catch (Throwable e
) {
181 reader
= new LineNumberReader(new InputStreamReader( in
));
182 String currentLine
= reader
.readLine();
183 StringBuffer buf
= new StringBuffer();
184 while(currentLine
!= null) {
186 int currentLineNumber
= reader
.getLineNumber();
187 if(currentLineNumber
== this.lineNumber
) {
188 buf
.append(currentLineNumber
)
193 else if(currentLineNumber
== this.lineNumber
+ 1) {
194 buf
.append(currentLineNumber
)
196 .append(currentLine
);
199 currentLine
= reader
.readLine();
201 this.codeSnippet
= buf
.toString().split("\n");
205 catch (IOException e
) {
206 LOG
.warn("[GrailsWrappedRuntimeException] I/O error reading line diagnostics: " + e
.getMessage(), e
);
212 } catch (IOException e
) {
220 * @return Returns the line.
222 public String
[] getCodeSnippet() {
223 return this.codeSnippet
;
227 * @return Returns the className.
229 public String
getClassName() {
234 * @return Returns the lineNumber.
236 public int getLineNumber() {
241 * @return Returns the stackTrace.
243 public String
getStackTraceText() {
248 * @return Returns the stackTrace lines
250 public String
[] getStackTraceLines() {
251 return stackTraceLines
;
255 * @see groovy.lang.GroovyRuntimeException#getMessage()
257 public String
getMessage() {
258 return cause
.getMessage();