GRAILS-1019: Allowing expressions to be used with the 'disabled' attribute for g...
[grails.git] / src / web / org / codehaus / groovy / grails / web / errors / GrailsWrappedRuntimeException.java
blobeba893e2c705b50e2af6be529b5f2f3fcac90580
1 /*
2 * Copyright 2004-2005 the original author or authors.
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
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;
33 import java.io.*;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
37 /**
38 * An exception that wraps a Grails RuntimeException and attempts to extract more relevent diagnostic messages from the stack trace
40 * @author Graeme Rocher
41 * @since 0.1
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";
64 /**
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);
70 this.cause = 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) {
78 cause = t;
79 break;
81 cause = t.getCause();
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();
92 sem.write(pw);
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();
101 else {
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);
106 try {
107 if(gsp.find()) {
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;
112 else {
113 if(m1.find()) {
114 this.className = m1.group(1);
115 this.lineNumber = Integer.parseInt(m1.group(2));
117 else if(m2.find()) {
118 this.className = m2.group(1);
119 this.lineNumber = Integer.parseInt(m2.group(2));
123 catch(NumberFormatException nfex) {
124 // ignore
128 LineNumberReader reader = null;
129 try {
130 if(cause instanceof SourceCodeAware && className.equals(UNKNOWN)) {
131 className = ((SourceCodeAware)cause).getFileName();
134 if(getLineNumber() > -1) {
135 String url;
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;
154 else {
155 url = gspFile;
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);
169 if(in == null) {
170 try {
171 Resource r = resolver.getResource("grails-app" + urlPrefix + fileName);
172 in = r.getInputStream();
173 } catch (Throwable e) {
174 // ignore
180 if(in != null) {
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)
189 .append(": ")
190 .append(currentLine)
191 .append("\n");
193 else if(currentLineNumber == this.lineNumber + 1) {
194 buf.append(currentLineNumber)
195 .append(": ")
196 .append(currentLine);
197 break;
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);
208 finally {
209 if(reader != null)
210 try {
211 reader.close();
212 } catch (IOException e) {
213 // ignore
220 * @return Returns the line.
222 public String[] getCodeSnippet() {
223 return this.codeSnippet;
227 * @return Returns the className.
229 public String getClassName() {
230 return className;
234 * @return Returns the lineNumber.
236 public int getLineNumber() {
237 return lineNumber;
241 * @return Returns the stackTrace.
243 public String getStackTraceText() {
244 return stackTrace;
248 * @return Returns the stackTrace lines
250 public String[] getStackTraceLines() {
251 return stackTraceLines;
254 /* (non-Javadoc)
255 * @see groovy.lang.GroovyRuntimeException#getMessage()
257 public String getMessage() {
258 return cause.getMessage();