|
15 | 15 | */ |
16 | 16 | package org.grails.web.errors; |
17 | 17 |
|
18 | | -import java.io.IOException; |
19 | | -import java.io.InputStream; |
20 | | -import java.io.InputStreamReader; |
21 | | -import java.io.LineNumberReader; |
22 | | -import java.lang.reflect.Constructor; |
23 | | -import java.util.regex.Matcher; |
24 | | -import java.util.regex.Pattern; |
25 | | - |
26 | 18 | import jakarta.servlet.ServletContext; |
27 | 19 |
|
28 | | -import org.apache.commons.logging.Log; |
29 | | -import org.apache.commons.logging.LogFactory; |
30 | | -import org.codehaus.groovy.control.MultipleCompilationErrorsException; |
31 | | -import org.codehaus.groovy.control.messages.SyntaxErrorMessage; |
32 | | -import org.springframework.core.io.Resource; |
33 | | -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; |
34 | | -import org.springframework.util.ClassUtils; |
35 | | -import org.springframework.util.ReflectionUtils; |
36 | | -import org.springframework.web.context.WebApplicationContext; |
37 | | -import org.springframework.web.context.support.WebApplicationContextUtils; |
38 | | - |
39 | | -import grails.artefact.ArtefactTypes; |
40 | | -import grails.core.GrailsApplication; |
41 | | -import grails.util.GrailsStringUtils; |
42 | | - |
43 | | -import org.grails.buffer.FastStringPrintWriter; |
44 | 20 | import org.grails.core.exceptions.GrailsException; |
45 | | -import org.grails.exceptions.reporting.SourceCodeAware; |
46 | | -import org.grails.gsp.ResourceAwareTemplateEngine; |
47 | | -import org.grails.io.support.GrailsFactoriesLoader; |
48 | | -import org.grails.web.servlet.mvc.GrailsWebRequest; |
49 | | -import org.grails.web.util.GrailsApplicationAttributes; |
50 | 21 |
|
51 | 22 | /** |
52 | | - * Wraps a Grails RuntimeException and attempts to extract more relevent diagnostic messages |
| 23 | + * Wraps a Grails RuntimeException and attempts to extract more relevant diagnostic messages |
53 | 24 | * from the stack trace. |
54 | 25 | * |
55 | 26 | * @author Graeme Rocher |
|
58 | 29 | */ |
59 | 30 | public class GrailsWrappedRuntimeException extends GrailsException { |
60 | 31 |
|
61 | | - public static final String URL_PREFIX = "/WEB-INF/grails-app/"; |
62 | | - |
63 | | - private static final Log logger = LogFactory.getLog(GrailsWrappedRuntimeException.class); |
64 | | - |
65 | | - private static final Class<? extends GrailsApplicationAttributes> grailsApplicationAttributesClass = GrailsFactoriesLoader.loadFactoryClasses( |
66 | | - GrailsApplicationAttributes.class, GrailsWebRequest.class.getClassLoader()).get(0); |
67 | | - |
68 | | - private static final Constructor<? extends GrailsApplicationAttributes> grailsApplicationAttributesConstructor = |
69 | | - ClassUtils.getConstructorIfAvailable(grailsApplicationAttributesClass, ServletContext.class); |
70 | | - |
71 | 32 | private static final long serialVersionUID = 7284065617154554366L; |
72 | 33 |
|
73 | | - private static final Pattern ANY_GSP_DETAILS = Pattern.compile("_gsp.run"); |
74 | | - |
75 | | - private static final Pattern PARSE_DETAILS_STEP1 = Pattern.compile("\\((\\w+)\\.groovy:(\\d+)\\)"); |
76 | | - |
77 | | - private static final Pattern PARSE_DETAILS_STEP2 = Pattern.compile("at\\s{1}(\\w+)\\$_closure\\d+\\.doCall\\(\\1:(\\d+)\\)"); |
78 | | - |
79 | | - private static final Pattern PARSE_GSP_DETAILS_STEP1 = Pattern.compile("_gsp\\.run\\(((\\w+?)_.*?):(\\d+)\\)"); |
80 | | - |
81 | | - private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); |
82 | | - |
83 | | - private static final String UNKNOWN = "Unknown"; |
84 | | - |
85 | | - private String className = UNKNOWN; |
86 | | - |
87 | | - private int lineNumber = -1; |
88 | | - |
89 | 34 | private final Throwable cause; |
90 | 35 |
|
91 | | - private final String stackTrace; |
92 | | - |
93 | | - private final String[] stackTraceLines; |
94 | | - |
95 | | - private String[] codeSnippet = new String[0]; |
96 | | - |
97 | | - private String gspFile; |
98 | | - |
99 | | - private String fileName; |
100 | | - |
101 | 36 | /** |
102 | 37 | * @param servletContext The ServletContext instance |
103 | 38 | * @param t The exception that was thrown |
104 | 39 | */ |
105 | 40 | public GrailsWrappedRuntimeException(ServletContext servletContext, Throwable t) { |
106 | 41 | super(t.getMessage(), t); |
107 | 42 | this.cause = t; |
108 | | - Throwable cause = t; |
109 | | - |
110 | | - FastStringPrintWriter pw = FastStringPrintWriter.newInstance(); |
111 | | - cause.printStackTrace(pw); |
112 | | - this.stackTrace = pw.toString(); |
113 | | - |
114 | | - while (cause.getCause() != cause) { |
115 | | - if (cause.getCause() == null) { |
116 | | - break; |
117 | | - } |
118 | | - cause = cause.getCause(); |
119 | | - } |
120 | | - |
121 | | - this.stackTraceLines = this.stackTrace.split("\\n"); |
122 | | - |
123 | | - if (cause instanceof MultipleCompilationErrorsException) { |
124 | | - MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) cause; |
125 | | - Object message = mcee.getErrorCollector().getErrors().iterator().next(); |
126 | | - if (message instanceof SyntaxErrorMessage) { |
127 | | - SyntaxErrorMessage sem = (SyntaxErrorMessage) message; |
128 | | - this.lineNumber = sem.getCause().getLine(); |
129 | | - this.className = sem.getCause().getSourceLocator(); |
130 | | - sem.write(pw); |
131 | | - } |
132 | | - } |
133 | | - else { |
134 | | - Matcher m1 = PARSE_DETAILS_STEP1.matcher(this.stackTrace); |
135 | | - Matcher m2 = PARSE_DETAILS_STEP2.matcher(this.stackTrace); |
136 | | - Matcher gsp = PARSE_GSP_DETAILS_STEP1.matcher(this.stackTrace); |
137 | | - try { |
138 | | - if (ANY_GSP_DETAILS.matcher(this.stackTrace).find() && gsp.find()) { |
139 | | - System.out.println(gsp.group(1) + " " + gsp.group(2) + " " + gsp.group(3)); |
140 | | - this.className = gsp.group(1); |
141 | | - this.lineNumber = Integer.parseInt(gsp.group(3)); |
142 | | - this.gspFile = URL_PREFIX + "views/" + gsp.group(2) + '/' + this.className; |
143 | | - } |
144 | | - else { |
145 | | - if (m1.find()) { |
146 | | - do { |
147 | | - this.className = m1.group(1); |
148 | | - this.lineNumber = Integer.parseInt(m1.group(2)); |
149 | | - } |
150 | | - while (m1.find()); |
151 | | - } |
152 | | - else { |
153 | | - while (m2.find()) { |
154 | | - this.className = m2.group(1); |
155 | | - this.lineNumber = Integer.parseInt(m2.group(2)); |
156 | | - } |
157 | | - } |
158 | | - } |
159 | | - } |
160 | | - catch (NumberFormatException ignored) { |
161 | | - } |
162 | | - } |
163 | | - |
164 | | - LineNumberReader reader = null; |
165 | | - try { |
166 | | - checkIfSourceCodeAware(t); |
167 | | - checkIfSourceCodeAware(cause); |
168 | | - |
169 | | - if (getLineNumber() > -1) { |
170 | | - String fileLocation; |
171 | | - String url = null; |
172 | | - |
173 | | - if (this.fileName != null) { |
174 | | - fileLocation = this.fileName; |
175 | | - } |
176 | | - else { |
177 | | - String urlPrefix = ""; |
178 | | - if (this.gspFile == null) { |
179 | | - this.fileName = this.className.replace('.', '/') + ".groovy"; |
180 | | - |
181 | | - GrailsApplication application = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext) |
182 | | - .getBean(GrailsApplication.APPLICATION_ID, GrailsApplication.class); |
183 | | - // @todo Refactor this to get the urlPrefix from the ArtefactHandler |
184 | | - if (application.isArtefactOfType(ArtefactTypes.CONTROLLER, this.className)) { |
185 | | - urlPrefix += "/controllers/"; |
186 | | - } |
187 | | - else if (application.isArtefactOfType("TagLib", this.className)) { |
188 | | - urlPrefix += "/taglib/"; |
189 | | - } |
190 | | - else if (application.isArtefactOfType(ArtefactTypes.SERVICE, this.className)) { |
191 | | - urlPrefix += "/services/"; |
192 | | - } |
193 | | - url = URL_PREFIX + urlPrefix + this.fileName; |
194 | | - } |
195 | | - else { |
196 | | - url = this.gspFile; |
197 | | - try { |
198 | | - WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext); |
199 | | - if (webApplicationContext != null) { |
200 | | - ResourceAwareTemplateEngine engine = |
201 | | - webApplicationContext.getBean(ResourceAwareTemplateEngine.BEAN_ID, ResourceAwareTemplateEngine.class); |
202 | | - this.lineNumber = engine.mapStackLineNumber(url, this.lineNumber); |
203 | | - } |
204 | | - } |
205 | | - catch (Exception e) { |
206 | | - ReflectionUtils.rethrowRuntimeException(e); |
207 | | - } |
208 | | - } |
209 | | - fileLocation = "grails-app" + urlPrefix + this.fileName; |
210 | | - } |
211 | | - |
212 | | - InputStream in = null; |
213 | | - if (!GrailsStringUtils.isBlank(url)) { |
214 | | - in = servletContext.getResourceAsStream(url); |
215 | | - logger.debug("Attempting to display code snippet found in url " + url); |
216 | | - } |
217 | | - if (in == null) { |
218 | | - Resource r = null; |
219 | | - try { |
220 | | - r = this.resolver.getResource(fileLocation); |
221 | | - in = r.getInputStream(); |
222 | | - } |
223 | | - catch (Throwable e) { |
224 | | - r = this.resolver.getResource("file:" + fileLocation); |
225 | | - if (r.exists()) { |
226 | | - try { |
227 | | - in = r.getInputStream(); |
228 | | - } |
229 | | - catch (IOException ignored) { |
230 | | - } |
231 | | - } |
232 | | - } |
233 | | - } |
234 | | - |
235 | | - if (in != null) { |
236 | | - reader = new LineNumberReader(new InputStreamReader(in, "UTF-8")); |
237 | | - String currentLine = reader.readLine(); |
238 | | - StringBuilder buf = new StringBuilder(); |
239 | | - while (currentLine != null) { |
240 | | - int currentLineNumber = reader.getLineNumber(); |
241 | | - if ((this.lineNumber > 0 && currentLineNumber == this.lineNumber - 1) || |
242 | | - (currentLineNumber == this.lineNumber)) { |
243 | | - buf.append(currentLineNumber) |
244 | | - .append(": ") |
245 | | - .append(currentLine) |
246 | | - .append("\n"); |
247 | | - } |
248 | | - else if (currentLineNumber == this.lineNumber + 1) { |
249 | | - buf.append(currentLineNumber) |
250 | | - .append(": ") |
251 | | - .append(currentLine); |
252 | | - break; |
253 | | - } |
254 | | - currentLine = reader.readLine(); |
255 | | - } |
256 | | - this.codeSnippet = buf.toString().split("\n"); |
257 | | - } |
258 | | - } |
259 | | - } |
260 | | - catch (IOException e) { |
261 | | - logger.warn("[GrailsWrappedRuntimeException] I/O error reading line diagnostics: " + e.getMessage(), e); |
262 | | - } |
263 | | - finally { |
264 | | - if (reader != null) { |
265 | | - try { |
266 | | - reader.close(); |
267 | | - } |
268 | | - catch (IOException ignored) { |
269 | | - } |
270 | | - } |
271 | | - } |
272 | | - } |
273 | | - |
274 | | - private void checkIfSourceCodeAware(Throwable t) { |
275 | | - if (!(t instanceof SourceCodeAware)) { |
276 | | - return; |
277 | | - } |
278 | | - |
279 | | - SourceCodeAware codeAware = (SourceCodeAware) t; |
280 | | - if (codeAware.getFileName() != null) { |
281 | | - this.fileName = codeAware.getFileName(); |
282 | | - if (this.className == null || UNKNOWN.equals(this.className)) { |
283 | | - this.className = codeAware.getFileName(); |
284 | | - } |
285 | | - } |
286 | | - if (codeAware.getLineNumber() > -1) { |
287 | | - this.lineNumber = codeAware.getLineNumber(); |
288 | | - } |
289 | | - } |
290 | | - |
291 | | - /** |
292 | | - * @return Returns the line. |
293 | | - */ |
294 | | - public String[] getCodeSnippet() { |
295 | | - return this.codeSnippet; |
296 | | - } |
297 | | - |
298 | | - /** |
299 | | - * @return Returns the className. |
300 | | - */ |
301 | | - public String getClassName() { |
302 | | - return this.className; |
303 | 43 | } |
304 | 44 |
|
305 | | - /** |
306 | | - * @return Returns the lineNumber. |
307 | | - */ |
308 | | - public int getLineNumber() { |
309 | | - return this.lineNumber; |
310 | | - } |
311 | | - |
312 | | - /** |
313 | | - * @return Returns the stackTrace. |
314 | | - */ |
315 | | - public String getStackTraceText() { |
316 | | - return this.stackTrace; |
317 | | - } |
318 | | - |
319 | | - /** |
320 | | - * @return Returns the stackTrace lines |
321 | | - */ |
322 | | - public String[] getStackTraceLines() { |
323 | | - return this.stackTraceLines; |
| 45 | + @Override |
| 46 | + public Throwable getCause() { |
| 47 | + return this.cause; |
324 | 48 | } |
325 | 49 |
|
326 | | - /* (non-Javadoc) |
327 | | - * @see groovy.lang.GroovyRuntimeException#getMessage() |
328 | | - */ |
329 | 50 | @Override |
330 | 51 | public String getMessage() { |
331 | 52 | return this.cause.getMessage(); |
|
0 commit comments