I am trying to override the default controller redirect method and cannot seem to get the following bit of code to work.
I have created a plugin and I'm trying to use the "doWithDynamicMethods" to replace the redirect.
def doWithDynamicMethods = {ctx ->
application.controllerClasses.each() { controllerClass ->
replaceRedirectMethod(controllerClass)
}
}
void replaceRedirectMethod(controllerClass) {
def oldRedirect = controllerClass.metaClass.pickMethod("redirect", [Map] as Class[])
controllerClass.metaClass.redirect = { Map args, Map params ->
// never seems to get here
}
}
Do I have the signature wrong or am I missing something? The reason I'm doing this is I'd like to change the uri of the redirect if a certain condition is met but with logging/print statements I see that it is going in the "replaceRedirectMethod" upon application startup/compile but it doesn't go in there when doing a redirect via the controller once the app is started.
Yes, the signature is wrong - redirect takes a single Map parameter (see the declaration in org.codehaus.groovy.grails.plugins.web.ControllersGrailsPlugin.registerControllerMethods())
So it should be
controllerClass.metaClass.redirect = { Map args ->
// pre-redirect logic
oldRedirect.invoke delegate, args
// post-redirect logic
}
Also note that if you want the redirect method override to be reapplied after modifying the source code of a controller, you need to do the following:
def watchedResources = [
"file:./grails-app/controllers/**/*Controller.groovy"]
def onChange = { event ->
if(!(event.source instanceof Class)) return
if(application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source))
{
replaceRedirectMethod(application.getArtefact(ControllerArtefactHandler.TYPE,
event.source.name))
}
}
Related
I have been trying to use a path variable in grails controller but I am not able to achieve it.
The intention behind is to validate the parameter submitted to the url which I need to make mandatory. I could not achieve it through RequestParam so I switched to PathVariable so that the url submitted without the required param should be filtered off by grails controller itself rather than me adding if/else checks for validity.
So, I can illustrate as below:
My URL is something as below:-
'<appcontext>/<controller>/<action>?<paramName>=<something>'
Now, to make 'paramName' mandatory I am not finding any way in Grails(Spring MVC provides #RequestParam annotation which can enable me for 'required' as true).
Another alternative I thought was to use path variables so that 'paramName' can be included in URL itself. So I tried like following:
'<appcontext>/<controller>/<action>/$paramName'
For validating the above URL I wrote specific mapping but some how it does not work too..
Following is the specific mapping I wrote:-
"/<controllerName>/<action>/$paramName" {
controller:<controller to take request>
action:<action to do task>
constraints {
paramName(nullable: false,empty:false, blank: false)
}
}
I tried to use spring annotation like #PathVariable and #RequestParam in controller as given below:-
def action(#PathVariable("paramName") String param){
//code goes here
}
If you name the method argument the same as the request parameter rename, Grails will take care of it for you...
// In UrlMappings.groovy
"/foo/$someVariable/$someOtherVariable" {
controller = 'demo'
action = 'magic'
}
Then in your controller:
// grails-app/controllers/com/demo/DemoController.groovy
class DemoController {
def magic(String someOtherVariable, String someVariable) {
// a request to /foo/jeff/brown will result in
// this action being invoked, someOtherVariable will be
// "brown" and someVariable will be "jeff"
}
}
I hope that helps.
EDIT:
Another option...
If for some reason you want different names for the method arguments you can explicitly map a method argument to a request parameter like this...
import grails.web.RequestParameter
class DemoController {
def magic(#RequestParameter('someVariable') String s1,
#RequestParameter('someOtherVariable') String s2) {
// a request to /foo/jeff/brown will result in
// this action being invoked, s2 will be
// "brown" and s1 will be "jeff"
}
}
Grails 1..3.7
In regular controller actions I can do the following:
def someAction = { OneCommand oneCmd, TwoCommand twoCmd ->
}
However, in a webflow action I have the equivillent:
def someFlow = {
someAction {
on("something") { OneCommand oneCmd, TwoCommand twoCmd ->
}
}
}
which doesn't bind the params to either object. If I remove one of them, it binds to the one left. Is this a bug or expected behavior?
I need to read out all available actions from any controller in my web-app. The reason for this is an authorization system where I need to give users a list of allowed actions.
E.g.:
User xyz has the authorization for executing the actions show, list, search.
User admin has the authorization for executing the actions edit, delete etc.
I need to read out all actions from a controller. Does anyone has an idea?
This will create a List of Maps (the 'data' variable) with controller information. Each element in the List is a Map with keys 'controller', corresponding to the URL name of the controller (e.g. BookController -> 'book'), controllerName corresponding to the class name ('BookController'), and 'actions' corresponding to a List of action names for that controller:
import org.springframework.beans.BeanWrapper
import org.springframework.beans.PropertyAccessorFactory
def data = []
for (controller in grailsApplication.controllerClasses) {
def controllerInfo = [:]
controllerInfo.controller = controller.logicalPropertyName
controllerInfo.controllerName = controller.fullName
List actions = []
BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(controller.newInstance())
for (pd in beanWrapper.propertyDescriptors) {
String closureClassName = controller.getPropertyOrStaticPropertyOrFieldValue(pd.name, Closure)?.class?.name
if (closureClassName) actions << pd.name
}
controllerInfo.actions = actions.sort()
data << controllerInfo
}
Here's an example that works with Grails 2, i.e it will capture actions defined as either methods or closures
import org.codehaus.groovy.grails.commons.DefaultGrailsControllerClass
import java.lang.reflect.Method
import grails.web.Action
// keys are logical controller names, values are list of action names
// that belong to that controller
def controllerActionNames = [:]
grailsApplication.controllerClasses.each { DefaultGrailsControllerClass controller ->
Class controllerClass = controller.clazz
// skip controllers in plugins
if (controllerClass.name.startsWith('com.mycompany')) {
String logicalControllerName = controller.logicalPropertyName
// get the actions defined as methods (Grails 2)
controllerClass.methods.each { Method method ->
if (method.getAnnotation(Action)) {
def actions = controllerActionNames[logicalControllerName] ?: []
actions << method.name
controllerActionNames[logicalControllerName] = actions
}
}
}
}
Grails does not support a straightforward way to do this. However, I was able to put together a puzzle from available grails methods and have come to this solution:
def actions = new HashSet<String>()
def controllerClass = grailsApplication.getArtefactInfo(ControllerArtefactHandler.TYPE)
.getGrailsClassByLogicalPropertyName(controllerName)
for (String uri : controllerClass.uris ) {
actions.add(controllerClass.getMethodActionName(uri) )
}
Variables grailsApplication and controllerName are injected by grails.
As controller itself does not have necessary methods, this code retrieves its controllerClass (see GrailsControllerClass), which has what we need: property uris and method getMethodActionName
To print out a list of all the methods with action names:
grailsApplication.controllerClasses.each {
it.getURIs().each {uri ->
println "${it.logicalPropertyName}.${it.getMethodActionName(uri)}"
}
}
I had to pull a list of all controllers and their respective URI. This is what I did on a grails 3.1.6 application.
grailsApplication.controllerClasses.each { controllerArtefact ->
def controllerClass = controllerArtefact.getClazz()
def actions = controllerArtefact.getActions()
actions?.each{action->
def controllerArtefactString = controllerArtefact.toString()
def controllerOnly = controllerArtefactString.split('Artefact > ')[1]
println "$controllerOnly >>>> $controllerOnly/${action.toString()}"
}
}
Wanna do the following:
BootStrap {
def init = {servletContext ->
........
MyDomainClass.metaClass.save = {->
delegate.extraSave()
//////// how to call original save() here?
}
}
.........
}
P.S. MyDomainClass#extraSave is defined as public void extraSave(){.....}
First of all, Bootstrap.groovy may not be the best place to do this kind of metaprogramming. The problem with this approach is that the changes to the classes will be applied when the application starts, but you may lose these changes when the application is reloaded. Obviously this is only an issue during development, and not an issue at all if you don't mind restarting the server every time you make a change, but I'll bet this would quickly become a major annoyance. In order to have the changes applied when app is reloaded as well, you should move the metaprogramming into a plugin, where you can hook into the onChange application lifecycle event.
So the steps are:
Create a plugin
Do the metaprogramming in the doWithDynamicMethods and onChange closures of the plugin descriptor
Here's a complete example where I "override" the chain() method on all the controller classes. The code to do likewise for the save() method of domain classes should only require some obvious replacements, e.g. use application.domainClasses instead of application.controllerClasses
def doWithDynamicMethods = {ctx ->
application.controllerClasses.each {controller ->
replaceChain(controller)
}
}
def onChange = {event ->
if (application.isArtefactOfType(ControllerArtefactHandler.TYPE, event.source)) {
def clz = application.getControllerClass(event.source?.name)
replaceChain(clz)
}
}
private replaceChain(controllerClass) {
// Save a reference to the grails chain() method
def grailsChain = controllerClass.metaClass.pickMethod("chain", [Map] as Class[])
controllerClass.metaClass.chain = {Map params ->
println "My code to execute before chain goes here"
// Invoke the grails chain() method
grailsChain.invoke(delegate, [params] as Object[])
println "My code to execute after chain goes here"
}
}
why not leveraging the GORM events for this purpose? In the Domain class:
def extraSave() {
// ...
}
def beforeInsert = {
extraSave()
}
def beforeUpdate = {
extraSave()
}
IMHO this a cleaner approach. Documentation can be found here
Not sure if the following works, but this might be a solution:
MyDomainClass.metaClass.origSave = MyDomainClass.metaClass.save
MyDomainClass.metaClass.save = {->
delegate.extraSave()
delegate.origSave()
}
Please give me feedbeck if the above worked...
In a Grails application I would like to add a foo() method to all my controller classes. I know that I can do this inside a plugin's doWithDynamicMethods closure using code like:
application.controllerClasses.toList()*.metaClass*.foo = { println 'foo called' }
However, I don't want to create a plugin just for this purpose. Is there anywhere else I can do this. I suspect it might be possible within the init closure of BootStrap.groovy, but I don't know how to get access to the GrailsApplication instance in this closure.
Thanks,
Don
def grailsApplication = org.codehaus.groovy.grails.commons.ApplicationHolder.application
The question was asked a long time ago, so my answer might not have been possible back then - but now it's April 2014. This code shows the most straightforward way of adding a method to all your controllers (from within BootStrap.init):
grailsApplication.controllerClasses.each { controllerClass ->
if (controllerClass.clazz.name.contains("org.idurkan.foo")) {
controllerClass.metaClass.respondError = { String message, int status = 404 ->
response.status = status
render([errorMessage: message] as JSON)
}
}
}
In BootStrap.groovy, inject grailsApplication like this: def grailsApplication
Make a call like mine above, substituting your own package name - to avoid messing around with plugins' classes.
Now in any controller you can use the repondError closure (invoke it like a method).
Note this does not add an action! It's just a utility closure/method available in every
controller.
class BootStrap {
def grailsApplication
def init = { servletContext ->
...
}
}