/
ControllersApi.java
495 lines (432 loc) · 19.5 KB
/
ControllersApi.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
/*
* Copyright 2011 SpringSource
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.grails.plugins.web.api;
import grails.util.CollectionUtils;
import grails.util.Environment;
import grails.util.GrailsNameUtils;
import groovy.lang.Closure;
import groovy.lang.GroovyObject;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections.Predicate;
import org.codehaus.groovy.grails.commons.ControllerArtefactHandler;
import org.codehaus.groovy.grails.commons.DomainClassArtefactHandler;
import org.codehaus.groovy.grails.commons.GrailsClassUtils;
import org.codehaus.groovy.grails.commons.GrailsDomainClassProperty;
import org.codehaus.groovy.grails.compiler.web.ControllerActionTransformer;
import org.codehaus.groovy.grails.plugins.GrailsPluginManager;
import org.codehaus.groovy.grails.web.binding.DataBindingUtils;
import org.codehaus.groovy.grails.web.controllers.ControllerExceptionHandlerMetaData;
import org.codehaus.groovy.grails.web.mapping.LinkGenerator;
import org.codehaus.groovy.grails.web.metaclass.ChainMethod;
import org.codehaus.groovy.grails.web.metaclass.ForwardMethod;
import org.codehaus.groovy.grails.web.metaclass.RedirectDynamicMethod;
import org.codehaus.groovy.grails.web.metaclass.RenderDynamicMethod;
import org.codehaus.groovy.grails.web.metaclass.WithFormMethod;
import org.codehaus.groovy.grails.web.plugins.support.WebMetaUtils;
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes;
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest;
import org.codehaus.groovy.grails.web.servlet.mvc.RedirectEventListener;
import org.codehaus.groovy.grails.web.servlet.mvc.exceptions.CannotRedirectException;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.grails.databinding.CollectionDataBindingSource;
import org.grails.databinding.DataBindingSource;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.validation.Errors;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.ModelAndView;
/**
* API for each controller in a Grails application.
*
* @author Graeme Rocher
* @since 2.0
*/
@SuppressWarnings("rawtypes")
public class ControllersApi extends CommonWebApi {
private static final String INCLUDE_MAP_KEY = "include";
private static final String EXCLUDE_MAP_KEY = "exclude";
private static final long serialVersionUID = 1;
protected static final String RENDER_METHOD_NAME = "render";
protected static final String BIND_DATA_METHOD = "bindData";
protected static final String SLASH = "/";
protected transient RedirectDynamicMethod redirect;
protected transient RenderDynamicMethod render;
protected transient WithFormMethod withFormMethod;
protected transient ForwardMethod forwardMethod;
public ControllersApi() {
this(null);
}
public ControllersApi(GrailsPluginManager pluginManager) {
super(pluginManager);
redirect = new RedirectDynamicMethod();
render = new RenderDynamicMethod();
withFormMethod = new WithFormMethod();
forwardMethod = new ForwardMethod();
}
public static ApplicationContext getStaticApplicationContext() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (!(requestAttributes instanceof GrailsWebRequest)) {
return null;
}
return ((GrailsWebRequest)requestAttributes).getApplicationContext();
}
public void setGspEncoding(String gspEncoding) {
render.setGspEncoding(gspEncoding);
}
public void setRedirectListeners(Collection<RedirectEventListener> redirectListeners) {
redirect.setRedirectListeners(redirectListeners);
}
public void setUseJessionId(boolean useJessionId) {
redirect.setUseJessionId(useJessionId);
}
public void setLinkGenerator(LinkGenerator linkGenerator) {
redirect.setLinkGenerator(linkGenerator);
}
/**
* Constructor used by controllers
*
* @param instance The instance
*/
public static void initialize(Object instance) {
ApplicationContext applicationContext = getStaticApplicationContext();
if (applicationContext == null) {
return;
}
applicationContext.getAutowireCapableBeanFactory().autowireBeanProperties(
instance, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);
if (Environment.getCurrent() == Environment.TEST) {
GrailsWebRequest webRequest = GrailsWebRequest.lookup();
if (webRequest != null) {
webRequest.setControllerName(GrailsNameUtils.getLogicalPropertyName(
instance.getClass().getName(), ControllerArtefactHandler.TYPE));
}
}
}
/**
* Returns the URI of the currently executing action
*
* @return The action URI
*/
public String getActionUri(Object instance) {
return SLASH + getControllerName(instance) + SLASH + getActionName(instance);
}
/**
* Returns the URI of the currently executing controller
* @return The controller URI
*/
public String getControllerUri(Object instance) {
return SLASH + getControllerName(instance);
}
/**
* Obtains a URI of a template by name
*
* @param name The name of the template
* @return The template URI
*/
public String getTemplateUri(Object instance, String name) {
return getGrailsAttributes(instance).getTemplateUri(name, getRequest(instance));
}
/**
* Obtains a URI of a view by name
*
* @param name The name of the view
* @return The template URI
*/
public String getViewUri(Object instance, String name) {
return getGrailsAttributes(instance).getViewUri(name, getRequest(instance));
}
/**
* Sets the errors instance of the current controller
*
* @param errors The error instance
*/
public void setErrors(Object instance, Errors errors) {
currentRequestAttributes().setAttribute(GrailsApplicationAttributes.ERRORS, errors, 0);
}
/**
* Obtains the errors instance for the current controller
*
* @return The Errors instance
*/
public Errors getErrors(Object instance) {
return (Errors)currentRequestAttributes().getAttribute(GrailsApplicationAttributes.ERRORS, 0);
}
/**
* Sets the ModelAndView of the current controller
*
* @param mav The ModelAndView
*/
public void setModelAndView(Object instance, ModelAndView mav) {
currentRequestAttributes().setAttribute(GrailsApplicationAttributes.MODEL_AND_VIEW, mav, 0);
}
/**
* Obtains the ModelAndView for the currently executing controller
*
* @return The ModelAndView
*/
public ModelAndView getModelAndView(Object instance) {
return (ModelAndView)currentRequestAttributes().getAttribute(GrailsApplicationAttributes.MODEL_AND_VIEW, 0);
}
/**
* Obtains the chain model which is used to chain request attributes from one request to the next via flash scope
* @return The chainModel
*/
public Map getChainModel(Object instance) {
return (Map)getFlash(instance).get("chainModel");
}
/**
* Return true if there are an errors
* @return true if there are errors
*/
public boolean hasErrors(Object instance) {
final Errors errors = getErrors(instance);
return errors != null && errors.hasErrors();
}
/**
* Redirects for the given arguments.
*
* @param args The arguments
* @return null
*/
public Object redirect(Object instance,Map args) {
return redirect.invoke(instance, "redirect", new Object[]{ args });
}
/**
* Redirects for the given arguments.
*
* @param object A domain class
* @return null
*/
@SuppressWarnings("unchecked")
public Object redirect(Object instance,Object object) {
if(object != null) {
Class<?> objectClass = object.getClass();
boolean isDomain = DomainClassArtefactHandler.isDomainClass(objectClass) && object instanceof GroovyObject;
if(isDomain) {
Object id = ((GroovyObject)object).getProperty(GrailsDomainClassProperty.IDENTITY);
if(id != null) {
Map args = new HashMap();
args.put(LinkGenerator.ATTRIBUTE_RESOURCE, object);
args.put(LinkGenerator.ATTRIBUTE_METHOD, HttpMethod.GET.toString());
return redirect(instance, args);
}
}
}
throw new CannotRedirectException("Cannot redirect for object ["+object+"] it is not a domain or has no identifier. Use an explicit redirect instead ");
}
/**
* Invokes the chain method for the given arguments
*
* @param instance The instance
* @param args The arguments
* @return Result of the redirect call
*/
public Object chain(Object instance, Map args) {
return ChainMethod.invoke(instance, args);
}
// the render method
public Object render(Object instance, Object o) {
return invokeRender(instance, DefaultGroovyMethods.inspect(o));
}
public Object render(Object instance, String txt) {
return invokeRender(instance, txt);
}
public Object render(Object instance, Map args) {
return invokeRender(instance, args);
}
public Object render(Object instance, Closure c) {
return invokeRender(instance, c);
}
public Object render(Object instance, Map args, Closure c) {
return invokeRender(instance, args, c);
}
protected Object invokeRender(Object instance, Object... args) {
return render.invoke(instance, RENDER_METHOD_NAME, args);
}
public Object bindData(Object instance, Object target, Object bindingSource, final List excludes) {
return bindData(instance, target, bindingSource, CollectionUtils.newMap(EXCLUDE_MAP_KEY, excludes), null);
}
public Object bindData(Object instance, Object target, Object bindingSource, final List excludes, String filter) {
return bindData(instance, target, bindingSource, CollectionUtils.newMap(EXCLUDE_MAP_KEY, excludes), filter);
}
public Object bindData(Object instance, Object target, Object bindingSource, Map includeExclude) {
return bindData(instance, target, bindingSource, includeExclude, null);
}
public Object bindData(Object instance, Object target, Object bindingSource, String filter) {
return bindData(instance, target, bindingSource, Collections.EMPTY_MAP, filter);
}
public Object bindData(Object instance, Object target, Object bindingSource) {
return bindData(instance, target, bindingSource, Collections.EMPTY_MAP, null);
}
public Object bindData(Object instance, Object target, Object bindingSource, Map includeExclude, String filter) {
List include = convertToListIfString(includeExclude.get(INCLUDE_MAP_KEY));
List exclude = convertToListIfString(includeExclude.get(EXCLUDE_MAP_KEY));
DataBindingUtils.bindObjectToInstance(target, bindingSource, include, exclude, filter);
return target;
}
public <T> void bindData(Object instance, Class<T> targetType, Collection<T> collectionToPopulate, ServletRequest request) throws Exception {
DataBindingUtils.bindToCollection(targetType, collectionToPopulate, request);
}
public <T> void bindData(Object instance, Class<T> targetType, Collection<T> collectionToPopulate, CollectionDataBindingSource collectionBindingSource) throws Exception {
DataBindingUtils.bindToCollection(targetType, collectionToPopulate, collectionBindingSource);
}
@SuppressWarnings("unchecked")
private List convertToListIfString(Object o) {
if (o instanceof String) {
List list = new ArrayList();
list.add(o);
o = list;
}
return (List) o;
}
/**
* Sets a response header for the given name and value
*
* @param instance The instance
* @param headerName The header name
* @param headerValue The header value
*/
public void header(Object instance, String headerName, Object headerValue) {
if (headerValue == null) {
return;
}
final HttpServletResponse response = getResponse(instance);
if (response != null) {
response.setHeader(headerName, headerValue.toString());
}
}
/**
* Used the synchronizer token pattern to avoid duplicate form submissions
*
* @param instance The instance
* @param callable The closure to execute
* @return The result of the closure execution
*/
public Object withForm(Object instance, Closure callable) {
return withFormMethod.withForm(getWebRequest(instance), callable);
}
/**
* Forwards a request for the given parameters using the RequestDispatchers forward method
*
* @param instance The instance
* @param params The parameters
* @return The forwarded URL
*/
public String forward(Object instance, Map params) {
return forwardMethod.forward(getRequest(instance), getResponse(instance), params);
}
/**
* Initializes a command object.
*
* If type is a domain class and the request body or parameters include an id, the id is used to retrieve
* the command object instance from the database, otherwise the no-arg constructor on type is invoke. If
* an attempt is made to retrieve the command object instance from the database and no corresponding
* record is found, null is returned.
*
* The command object is then subjected to data binding and dependency injection before being returned.
*
*
* @param controllerInstance The controller instance
* @param type The type of the command object
* @return the initialized command object or null if the command object is a domain class, the body or
* parameters included an id and no corresponding record was found in the database.
*/
public Object initializeCommandObject(final Object controllerInstance, final Class type) throws Exception {
final HttpServletRequest request = getRequest(controllerInstance);
final DataBindingSource dataBindingSource = DataBindingUtils.createDataBindingSource(getGrailsApplication(controllerInstance), type, request);
final DataBindingSource commandObjectBindingSource = WebMetaUtils.getCommandObjectBindingSource(type, dataBindingSource);
final Object commandObjectInstance;
Object entityIdentifierValue = null;
if(DomainClassArtefactHandler.isDomainClass(type)) {
entityIdentifierValue = commandObjectBindingSource.getIdentifierValue();
if(entityIdentifierValue == null) {
final GrailsWebRequest webRequest = GrailsWebRequest.lookup(request);
entityIdentifierValue = webRequest != null ? webRequest.getParams().getIdentifier() : null;
}
}
if(entityIdentifierValue != null) {
commandObjectInstance = InvokerHelper.invokeStaticMethod(type, "get", entityIdentifierValue);
} else {
commandObjectInstance = type.newInstance();
}
if(commandObjectInstance != null) {
final boolean shouldDoDataBinding;
if(entityIdentifierValue != null) {
final HttpMethod requestMethod = HttpMethod.valueOf(request.getMethod());
switch(requestMethod) {
case PATCH:
case POST:
case PUT:
shouldDoDataBinding = true;
break;
default:
shouldDoDataBinding = false;
}
} else {
shouldDoDataBinding = true;
}
if(shouldDoDataBinding) {
bindData(controllerInstance, commandObjectInstance, commandObjectBindingSource, Collections.EMPTY_MAP, null);
}
final ApplicationContext applicationContext = getApplicationContext(controllerInstance);
final AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
autowireCapableBeanFactory.autowireBeanProperties(commandObjectInstance, AutowireCapableBeanFactory.AUTOWIRE_BY_NAME, false);
}
return commandObjectInstance;
}
@SuppressWarnings("unchecked")
public Method getExceptionHandlerMethodFor(final Object controllerInstance, final Class<? extends Exception> exceptionType) throws Exception {
if(!Exception.class.isAssignableFrom(exceptionType)) {
throw new IllegalArgumentException("exceptionType [" + exceptionType.getName() + "] argument must be Exception or a subclass of Exception");
}
Method handlerMethod = null;
final List<ControllerExceptionHandlerMetaData> exceptionHandlerMetaDataInstances = (List<ControllerExceptionHandlerMetaData>) GrailsClassUtils.getStaticFieldValue(controllerInstance.getClass(), ControllerActionTransformer.EXCEPTION_HANDLER_META_DATA_FIELD_NAME);
if(exceptionHandlerMetaDataInstances != null && exceptionHandlerMetaDataInstances.size() > 0) {
// find all of the handler methods which could accept this exception type
final List<ControllerExceptionHandlerMetaData> matches = (List<ControllerExceptionHandlerMetaData>) org.apache.commons.collections.CollectionUtils.select(exceptionHandlerMetaDataInstances, new Predicate() {
@Override
public boolean evaluate(Object object) {
ControllerExceptionHandlerMetaData md = (ControllerExceptionHandlerMetaData) object;
return md.getExceptionType().isAssignableFrom(exceptionType);
}
});
if(matches.size() > 0) {
ControllerExceptionHandlerMetaData theOne = matches.get(0);
// if there are more than 1, find the one that is farthest down the inheritance hierarchy
for(int i = 1; i < matches.size(); i++) {
final ControllerExceptionHandlerMetaData nextMatch = matches.get(i);
if(theOne.getExceptionType().isAssignableFrom(nextMatch.getExceptionType())) {
theOne = nextMatch;
}
}
handlerMethod = controllerInstance.getClass().getMethod(theOne.getMethodName(), theOne.getExceptionType());
}
}
return handlerMethod;
}
}