Skip to content

Commit 1622cf5

Browse files
authored
Merge pull request #1254 from rainboyan
* pr/1254: Refactor GrailsExceptionResolver - Introduce `GrailsUrlMappingsExceptionResolver` to make grace-web-mvc decoupled from grace-web-url-mappings - `GrailsExceptionResolver` should not reuse cached controller attribute to find the given error path - Cleanup `GrailsWrappedRuntimeException` Closes gh-1254
2 parents c843dd9 + 09ae9a3 commit 1622cf5

File tree

11 files changed

+598
-548
lines changed

11 files changed

+598
-548
lines changed

grace-plugin-controllers/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ dependencies {
88
api project(":grace-plugin-validation")
99
api project(":grace-web-gsp")
1010
api project(":grace-web-mvc")
11+
api project(":grace-web-url-mappings")
1112
api project(":grace-util")
1213

1314
compileOnly libs.jakarta.servlet

grace-plugin-controllers/src/main/groovy/org/grails/plugins/web/controllers/ControllersPluginConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.grails.web.errors.GrailsExceptionResolver;
5252
import org.grails.web.filters.HiddenHttpMethodFilter;
5353
import org.grails.web.filters.OrderedHiddenHttpMethodFilter;
54+
import org.grails.web.mapping.mvc.GrailsUrlMappingsExceptionResolver;
5455
import org.grails.web.servlet.mvc.GrailsDispatcherServlet;
5556
import org.grails.web.servlet.mvc.GrailsWebRequestFilter;
5657
import org.grails.web.servlet.mvc.ParameterCreationListener;
@@ -101,7 +102,7 @@ public StackTraceFilterer stackTraceFilterer(ObjectProvider<GrailsApplication> g
101102
@Bean
102103
public GrailsExceptionResolver exceptionHandler(ObjectProvider<GrailsApplication> grailsApplicationProvider,
103104
ObjectProvider<StackTraceFilterer> stackTraceFiltererObjectProvider) {
104-
GrailsExceptionResolver exceptionResolver = new GrailsExceptionResolver();
105+
GrailsUrlMappingsExceptionResolver exceptionResolver = new GrailsUrlMappingsExceptionResolver();
105106
exceptionResolver.setGrailsApplication(grailsApplicationProvider.getIfAvailable());
106107
exceptionResolver.setStackTraceFilterer(stackTraceFiltererObjectProvider.getIfAvailable());
107108

Lines changed: 30 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,34 @@
11
package org.grails.web.errors
22

3-
import grails.core.DefaultGrailsApplication
4-
import grails.core.GrailsApplication
5-
import grails.util.Environment
6-
import grails.util.GrailsWebMockUtil
7-
import grails.web.CamelCaseUrlConverter
8-
import grails.web.UrlConverter
9-
import grails.web.mapping.UrlMappingsHolder
10-
import org.grails.config.PropertySourcesConfig
11-
import org.grails.exceptions.reporting.DefaultStackTraceFilterer
12-
import org.grails.plugins.testing.GrailsMockHttpServletRequest
13-
import org.grails.plugins.testing.GrailsMockHttpServletResponse
14-
import org.grails.support.MockApplicationContext
15-
import org.grails.web.mapping.DefaultUrlMappingEvaluator
16-
import org.grails.web.mapping.DefaultUrlMappingsHolder
17-
import org.grails.web.servlet.view.CompositeViewResolver
183
import org.junit.jupiter.api.AfterEach
194
import org.junit.jupiter.api.BeforeEach
205
import org.junit.jupiter.api.Test
216
import org.springframework.mock.web.MockHttpServletRequest
22-
import org.springframework.mock.web.MockHttpServletResponse
237
import org.springframework.mock.web.MockServletContext
24-
import org.springframework.web.context.WebApplicationContext
258
import org.springframework.web.context.request.RequestContextHolder
26-
import org.springframework.web.multipart.support.StandardServletMultipartResolver
279
import org.springframework.web.servlet.View
2810
import org.springframework.web.servlet.ViewResolver
2911
import org.springframework.web.servlet.view.InternalResourceView
3012

31-
import static org.junit.jupiter.api.Assertions.*
13+
import grails.core.DefaultGrailsApplication
14+
import grails.core.GrailsApplication
15+
import grails.util.Environment
16+
import grails.web.CamelCaseUrlConverter
17+
import grails.web.UrlConverter
18+
19+
import org.grails.config.PropertySourcesConfig
20+
import org.grails.exceptions.reporting.DefaultStackTraceFilterer
21+
import org.grails.support.MockApplicationContext
22+
23+
import static org.junit.jupiter.api.Assertions.assertEquals
24+
import static org.junit.jupiter.api.Assertions.assertThrows
3225

3326
/**
3427
* Test case for {@link org.grails.web.errors.GrailsExceptionResolver}.
3528
*/
3629
class GrailsExceptionResolverTests {
3730

3831
private application = new DefaultGrailsApplication()
39-
private resolver = new GrailsExceptionResolver()
40-
private mockContext = new MockServletContext()
4132
private mockCtx = new MockApplicationContext()
4233

4334
@AfterEach
@@ -49,8 +40,8 @@ class GrailsExceptionResolverTests {
4940
void setUp() throws Exception {
5041
mockCtx.registerMockBean(GrailsApplication.APPLICATION_ID, new DefaultGrailsApplication())
5142
def mainContext = new MockApplicationContext();
52-
mainContext.registerMockBean(UrlConverter.BEAN_NAME, new CamelCaseUrlConverter());
53-
application.mainContext = mainContext
43+
mainContext.registerMockBean(UrlConverter.BEAN_NAME, new CamelCaseUrlConverter())
44+
application.mainContext = mainContext
5445
}
5546

5647
@Test
@@ -70,101 +61,6 @@ class GrailsExceptionResolverTests {
7061
}
7162
}
7263

73-
@Test
74-
void testResolveExceptionToView() {
75-
def mappings = new DefaultUrlMappingEvaluator(mockCtx).evaluateMappings {
76-
"500"(view:"myView")
77-
}
78-
79-
def urlMappingsHolder = new DefaultUrlMappingsHolder(mappings)
80-
def webRequest = GrailsWebMockUtil.bindMockWebRequest(mockCtx,
81-
new GrailsMockHttpServletRequest(), new GrailsMockHttpServletResponse())
82-
83-
mockCtx.registerMockBean UrlMappingsHolder.BEAN_ID, urlMappingsHolder
84-
ViewResolver viewResolver = new DummyViewResolver()
85-
mockCtx.registerMockBean "viewResolver", viewResolver
86-
mockCtx.registerMockBean 'grailsApplication', application
87-
mockCtx.registerMockBean CompositeViewResolver.BEAN_NAME, new CompositeViewResolver(viewResolvers: [viewResolver])
88-
mockContext.setAttribute WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, mockCtx
89-
90-
resolver.servletContext = mockContext
91-
resolver.exceptionMappings = ['java.lang.Exception': '/error'] as Properties
92-
resolver.grailsApplication = application
93-
resolver.stackFilterer = new DefaultStackTraceFilterer()
94-
95-
def ex = new Exception()
96-
def request = webRequest.currentRequest
97-
def response = webRequest.currentResponse
98-
def handler = new Object()
99-
def modelAndView = resolver.resolveException(request, response, handler, ex)
100-
101-
assertNotNull modelAndView, "should have returned a ModelAndView"
102-
assertEquals "/myView", modelAndView.view.url
103-
}
104-
105-
@Test
106-
void testResolveExceptionToController() {
107-
def mappings = new DefaultUrlMappingEvaluator(mockCtx).evaluateMappings {
108-
"500"(controller:"foo", action:"bar")
109-
}
110-
111-
def urlMappingsHolder = new DefaultUrlMappingsHolder(mappings)
112-
def webRequest = GrailsWebMockUtil.bindMockWebRequest()
113-
114-
mockCtx.registerMockBean UrlMappingsHolder.BEAN_ID, urlMappingsHolder
115-
mockCtx.registerMockBean "viewResolver", new DummyViewResolver()
116-
mockCtx.registerMockBean GrailsApplication.APPLICATION_ID, application
117-
mockCtx.registerMockBean "multipartResolver", new StandardServletMultipartResolver()
118-
mockContext.setAttribute WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, mockCtx
119-
120-
resolver.servletContext = mockContext
121-
resolver.exceptionMappings = ['java.lang.Exception': '/error'] as Properties
122-
resolver.grailsApplication = application
123-
resolver.stackFilterer = new DefaultStackTraceFilterer()
124-
125-
def ex = new Exception()
126-
def request = webRequest.currentRequest
127-
MockHttpServletResponse response = webRequest.currentResponse
128-
def handler = new Object()
129-
def modelAndView = resolver.resolveException(request, response, handler, ex)
130-
131-
assertNotNull modelAndView, "should have returned a ModelAndView"
132-
assertTrue modelAndView.empty
133-
134-
assertEquals "/foo/bar",response.getForwardedUrl()
135-
}
136-
137-
@Test
138-
void testResolveExceptionToControllerWhenResponseCommitted() {
139-
def mappings = new DefaultUrlMappingEvaluator(mockCtx).evaluateMappings {
140-
"500"(controller:"foo", action:"bar")
141-
}
142-
143-
def urlMappingsHolder = new DefaultUrlMappingsHolder(mappings)
144-
def webRequest = GrailsWebMockUtil.bindMockWebRequest()
145-
146-
mockCtx.registerMockBean UrlMappingsHolder.BEAN_ID, urlMappingsHolder
147-
mockCtx.registerMockBean "viewResolver", new DummyViewResolver()
148-
mockCtx.registerMockBean GrailsApplication.APPLICATION_ID, application
149-
mockCtx.registerMockBean "multipartResolver", new StandardServletMultipartResolver()
150-
mockContext.setAttribute WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, mockCtx
151-
152-
resolver.servletContext = mockContext
153-
resolver.exceptionMappings = ['java.lang.Exception': '/error'] as Properties
154-
resolver.grailsApplication = application
155-
resolver.stackFilterer = new DefaultStackTraceFilterer()
156-
157-
def ex = new Exception()
158-
def request = webRequest.currentRequest
159-
MockHttpServletResponse response = webRequest.currentResponse
160-
def handler = new Object()
161-
response.setCommitted(true)
162-
def modelAndView = resolver.resolveException(request, response, handler, ex)
163-
164-
assertNotNull modelAndView, "should have returned a ModelAndView"
165-
assertFalse modelAndView.empty
166-
}
167-
16864
@Test
16965
void testLogRequestWithException() {
17066
def config = new ConfigSlurper().parse('''
@@ -179,7 +75,7 @@ grails.exceptionresolver.params.exclude = ['jennysPhoneNumber']
17975
request.addParameter "jennysPhoneNumber", "8675309"
18076

18177
System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name)
182-
def resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
78+
def resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
18379
resolver.stackFilterer = new DefaultStackTraceFilterer()
18480
def msg = resolver.getRequestLogMessage(new RuntimeException("bad things happened"), request)
18581

@@ -210,7 +106,7 @@ grails.exceptionresolver.params.exclude = ['jennysPhoneNumber']
210106
request.addParameter "jennysPhoneNumber", "8675309"
211107

212108
System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name)
213-
def resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
109+
def resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
214110
resolver.stackFilterer = new DefaultStackTraceFilterer()
215111
def msg = resolver.getRequestLogMessage(request)
216112

@@ -251,19 +147,19 @@ Method: GET
251147
Filtered stacktrace:'''.replaceAll('[\n\r]', '')
252148

253149
System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name)
254-
def resolver = new GrailsExceptionResolver(grailsApplication:application)
150+
def resolver = new GrailsExceptionResolver(grailsApplication: application)
255151
resolver.stackFilterer = new DefaultStackTraceFilterer()
256152
def msg = resolver.getRequestLogMessage(request)
257153
assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '')
258154

259155
System.setProperty(Environment.KEY, Environment.PRODUCTION.name)
260-
resolver = new GrailsExceptionResolver(grailsApplication:application)
156+
resolver = new GrailsExceptionResolver(grailsApplication: application)
261157
resolver.stackFilterer = new DefaultStackTraceFilterer()
262158
msg = resolver.getRequestLogMessage(request)
263159
assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '')
264160

265161
System.setProperty(Environment.KEY, Environment.TEST.name)
266-
resolver = new GrailsExceptionResolver(grailsApplication:application)
162+
resolver = new GrailsExceptionResolver(grailsApplication: application)
267163
resolver.stackFilterer = new DefaultStackTraceFilterer()
268164
msg = resolver.getRequestLogMessage(request)
269165
assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '')
@@ -273,19 +169,19 @@ grails.exceptionresolver.logRequestParameters = false
273169
''')
274170

275171
System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name)
276-
resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
172+
resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
277173
resolver.stackFilterer = new DefaultStackTraceFilterer()
278174
msg = resolver.getRequestLogMessage(request)
279175
assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '')
280176

281177
System.setProperty(Environment.KEY, Environment.PRODUCTION.name)
282-
resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
178+
resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
283179
resolver.stackFilterer = new DefaultStackTraceFilterer()
284180
msg = resolver.getRequestLogMessage(request)
285181
assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '')
286182

287183
System.setProperty(Environment.KEY, Environment.TEST.name)
288-
resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
184+
resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
289185
resolver.stackFilterer = new DefaultStackTraceFilterer()
290186
msg = resolver.getRequestLogMessage(request)
291187
assertEquals msgWithoutParameters, msg.replaceAll('[\n\r]', '')
@@ -295,30 +191,34 @@ grails.exceptionresolver.logRequestParameters = true
295191
''')
296192

297193
System.setProperty(Environment.KEY, Environment.DEVELOPMENT.name)
298-
resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
194+
resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
299195
resolver.stackFilterer = new DefaultStackTraceFilterer()
300196
msg = resolver.getRequestLogMessage(request)
301197
assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '')
302198

303199
System.setProperty(Environment.KEY, Environment.PRODUCTION.name)
304-
resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
200+
resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
305201
resolver.stackFilterer = new DefaultStackTraceFilterer()
306202
msg = resolver.getRequestLogMessage(request)
307203
assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '')
308204

309205
System.setProperty(Environment.KEY, Environment.TEST.name)
310-
resolver = new GrailsExceptionResolver(grailsApplication:new DefaultGrailsApplication(config:new PropertySourcesConfig().merge(config)))
206+
resolver = new GrailsExceptionResolver(grailsApplication: new DefaultGrailsApplication(config: new PropertySourcesConfig().merge(config)))
311207
resolver.stackFilterer = new DefaultStackTraceFilterer()
312208
msg = resolver.getRequestLogMessage(request)
313209
assertEquals msgWithParameters, msg.replaceAll('[\n\r]', '')
314-
} finally {
210+
}
211+
finally {
315212
System.setProperty(Environment.KEY, oldEnvName)
316213
}
317214
}
318215
}
319216

320217
class DummyViewResolver implements ViewResolver {
218+
219+
@Override
321220
View resolveViewName(String viewName, Locale locale) {
322221
new InternalResourceView(viewName)
323222
}
223+
324224
}

grace-web-mvc/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
dependencies {
22
api project(":grace-gsp")
33
api project(":grace-web")
4-
api project(":grace-web-url-mappings")
54

65
compileOnlyApi libs.jakarta.servlet
76

0 commit comments

Comments
 (0)