2 * Copyright 2004-2005 the original author or authors.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org
.codehaus
.groovy
.grails
.scaffolding
;
18 import groovy
.lang
.Closure
;
19 import groovy
.lang
.GroovyObject
;
20 import org
.codehaus
.groovy
.grails
.scaffolding
.exceptions
.ScaffoldingException
;
21 import org
.codehaus
.groovy
.grails
.web
.metaclass
.ControllerDynamicMethods
;
22 import org
.codehaus
.groovy
.grails
.web
.metaclass
.RedirectDynamicMethod
;
23 import org
.codehaus
.groovy
.grails
.web
.metaclass
.RenderDynamicMethod
;
24 import org
.codehaus
.groovy
.grails
.web
.servlet
.mvc
.exceptions
.ControllerExecutionException
;
25 import org
.springframework
.web
.servlet
.ModelAndView
;
27 import javax
.servlet
.http
.HttpServletRequest
;
28 import javax
.servlet
.http
.HttpServletResponse
;
29 import java
.io
.IOException
;
30 import java
.lang
.reflect
.Constructor
;
31 import java
.lang
.reflect
.InvocationTargetException
;
32 import java
.util
.HashMap
;
35 * The default implementation of scaffolding for Grails domain class and controller. Implements the
36 * GrailsScaffolder interface.
38 * Requires a ScaffoldRequestHandler and ScaffoldResponseHandlerFactory. The ScaffoldRequestHandler is
39 * responsible for handling requests to CRUD operations, whilst the ScaffoldResponseHandlerFactory
40 * creates ScaffoldResponseHandler instances that are responsible for delivering the response
41 * to the user in whatever format is required.
43 * @see org.codehaus.groovy.grails.scaffolding.GrailsScaffolder
44 * @see org.codehaus.groovy.grails.scaffolding.ScaffoldRequestHandler
45 * @see org.codehaus.groovy.grails.scaffolding.ScaffoldResponseHandlerFactory
47 * @author Graeme Rocher
52 public class DefaultGrailsScaffolder
implements GrailsScaffolder
{
55 private ScaffoldRequestHandler scaffoldRequestHandler
;
56 private ScaffoldResponseHandlerFactory scaffoldResponseHandlerFactory
;
59 * Abstract base class that extends closure and retrieves the necessary arguments from the controller
60 * This is used to inject closure properties into controllers so controller actions appear as if by magic.
62 static abstract class AbstractAction
extends Closure
{
63 protected GroovyObject controller
;
64 protected HttpServletRequest request
;
65 protected HttpServletResponse response
;
66 protected ScaffoldRequestHandler scaffoldRequestHandler
;
67 protected ScaffoldResponseHandlerFactory scaffoldResponseFactory
;
68 protected ScaffoldResponseHandler scaffoldResponseHandler
;
70 public AbstractAction(Object owner
) {
72 controller
= (GroovyObject
)getOwner();
73 request
= (HttpServletRequest
)controller
.getProperty(ControllerDynamicMethods
.REQUEST_PROPERTY
);
74 response
= (HttpServletResponse
)controller
.getProperty(ControllerDynamicMethods
.RESPONSE_PROPERTY
);
78 * @param scaffoldRequestHandler The scaffoldRequestHandler to set.
80 public void setScaffoldRequestHandler(
81 ScaffoldRequestHandler scaffoldRequestHandler
) {
82 this.scaffoldRequestHandler
= scaffoldRequestHandler
;
85 public void setScaffoldResponseHandlerFactory(ScaffoldResponseHandlerFactory factory
) {
86 this.scaffoldResponseFactory
= factory
;
87 Object includeUri
= request
.getAttribute("javax.servlet.include.request_uri");
89 if (includeUri
!= null) {
90 uri
= (String
) includeUri
;
92 uri
= request
.getRequestURI();
94 this.scaffoldResponseHandler
= this.scaffoldResponseFactory
.getScaffoldResponseHandler(uri
);
99 * A closure that handles a call to a scaffolded list action
101 class ListAction
extends AbstractAction
{
102 public ListAction(Object owner
) {
107 * @see groovy.lang.Closure#call(java.lang.Object[])
109 public Object
call(Object
[] args
) {
110 Map model
= this.scaffoldRequestHandler
.handleList(request
,response
);
111 return scaffoldResponseHandler
.handleResponse(request
,response
,LIST_ACTION
,model
);
117 * A closure that handles a call to a scaffolded list action
119 class IndexAction
extends AbstractAction
{
120 public IndexAction(Object owner
) {
125 * @see groovy.lang.Closure#call(java.lang.Object[])
127 public Object
call(Object
[] args
) {
128 Map arguments
= new HashMap();
129 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ACTION
, LIST_ACTION
);
130 return controller
.invokeMethod(RedirectDynamicMethod
.METHOD_SIGNATURE
,new Object
[]{ arguments
});
136 * A closure that handles a call to a scaffolded list action
138 class CreateAction
extends AbstractAction
{
139 public CreateAction(Object owner
) {
144 * @see groovy.lang.Closure#call(java.lang.Object[])
146 public Object
call(Object
[] args
) {
147 Map model
= this.scaffoldRequestHandler
.handleCreate(request
,response
,new DefaultScaffoldCallback());
148 return scaffoldResponseHandler
.handleResponse(request
,response
,CREATE_ACTION
,model
);
154 * A closure action that implements showing a scaffolded instance by id. If the id is not
155 * specified it redirects to the "list" action
157 class ShowAction
extends AbstractAction
{
159 public ShowAction(Object owner
) {
164 * @see groovy.lang.Closure#call(java.lang.Object[])
166 public Object
call(Object
[] args
) {
168 ScaffoldCallback callback
= new DefaultScaffoldCallback();
169 Map model
= this.scaffoldRequestHandler
.handleShow(request
,response
, callback
);
171 if(callback
.isInvoked()) {
172 return scaffoldResponseHandler
.handleResponse(request
,response
,SHOW_ACTION
,model
);
175 Map arguments
= new HashMap();
176 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ACTION
, LIST_ACTION
);
177 return controller
.invokeMethod(RedirectDynamicMethod
.METHOD_SIGNATURE
,new Object
[]{ arguments
});
184 * A closure action that implements editing a scaffolded instance by id. At the moment it is the same
185 * as ShowAction, but could differ in the future as it may load other model instances to support editing
186 * an instance including the GrailsDomainClass instance
188 class EditAction
extends AbstractAction
{
190 public EditAction(Object owner
) {
195 * @see groovy.lang.Closure#call(java.lang.Object[])
197 public Object
call(Object
[] args
) {
199 ScaffoldCallback callback
= new DefaultScaffoldCallback();
200 Map model
= this.scaffoldRequestHandler
.handleShow(request
,response
, callback
);
202 if(callback
.isInvoked()) {
203 return scaffoldResponseHandler
.handleResponse(request
,response
,EDIT_ACTION
,model
);
206 Closure listAction
= (Closure
)controller
.getProperty(LIST_ACTION
);
207 Map arguments
= new HashMap();
208 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ACTION
, listAction
);
209 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ERRORS
, callback
.getErrors() );
210 return controller
.invokeMethod(RedirectDynamicMethod
.METHOD_SIGNATURE
,new Object
[]{ arguments
});
217 * A closure action that implements deletion of a scaffolded instance by id. The instance is deleted and then
218 * the action redirects to "list"
220 class DeleteAction
extends AbstractAction
{
222 public DeleteAction(Object owner
) {
227 * @see groovy.lang.Closure#call(java.lang.Object[])
229 public Object
call(Object
[] args
) {
230 if(!"POST".equals(request
.getMethod())) {
232 response
.sendError(HttpServletResponse
.SC_METHOD_NOT_ALLOWED
);
234 } catch (IOException e
) {
235 throw new ControllerExecutionException("I/O error sending 403 error",e
);
239 ScaffoldCallback callback
= new DefaultScaffoldCallback();
241 this.scaffoldRequestHandler
.handleDelete(request
,response
, callback
);
243 // now redirect to list
244 Closure listAction
= (Closure
)controller
.getProperty(LIST_ACTION
);
245 Map arguments
= new HashMap();
246 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ACTION
, listAction
);
247 return controller
.invokeMethod(RedirectDynamicMethod
.METHOD_SIGNATURE
,new Object
[]{ arguments
});
253 * A closure action that implements the saving of new scaffoled instances. If the instance is created successfully
254 * the action redirects to "show" for the id, otherwise it redirects to "create"
256 class SaveAction
extends AbstractAction
{
258 public SaveAction(Object owner
) {
263 * @see groovy.lang.Closure#call(java.lang.Object[])
265 public Object
call(Object
[] args
) {
266 if(!"POST".equals(request
.getMethod())) {
268 response
.sendError(HttpServletResponse
.SC_METHOD_NOT_ALLOWED
);
270 } catch (IOException e
) {
271 throw new ControllerExecutionException("I/O error sending 403 error",e
);
275 ScaffoldCallback callback
= new DefaultScaffoldCallback();
277 Map model
= this.scaffoldRequestHandler
.handleSave(request
,response
, callback
);
278 if(callback
.isInvoked()) {
279 Closure showAction
= (Closure
)controller
.getProperty(SHOW_ACTION
);
280 Map arguments
= new HashMap();
281 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ACTION
, showAction
);
282 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ID
, model
.get(RedirectDynamicMethod
.ARGUMENT_ID
) );
283 return controller
.invokeMethod(RedirectDynamicMethod
.METHOD_SIGNATURE
,new Object
[]{ arguments
});
287 Map arguments
= new HashMap();
288 arguments
.put( RenderDynamicMethod
.ARGUMENT_VIEW
, CREATE_ACTION
);
289 arguments
.put( RenderDynamicMethod
.ARGUMENT_MODEL
,model
);
290 controller
.invokeMethod(RenderDynamicMethod
.METHOD_SIGNATURE
,new Object
[]{ arguments
});
293 ModelAndView mv
= this.scaffoldResponseHandler
.handleResponse(request
, response
,CREATE_ACTION
,model
);
294 controller
.setProperty(ControllerDynamicMethods
.MODEL_AND_VIEW_PROPERTY
, mv
);
303 * A closure action that implements the updating of an existing scaffoled instances. If the instance is updated successfully
304 * the action redirects to "show" for the id, otherwise it redirects to "edit"
306 class UpdateAction
extends AbstractAction
{
308 public UpdateAction(Object owner
) {
313 * @see groovy.lang.Closure#call(java.lang.Object[])
315 public Object
call(Object
[] args
) {
316 if(!"POST".equals(request
.getMethod())) {
318 response
.sendError(HttpServletResponse
.SC_METHOD_NOT_ALLOWED
);
320 } catch (IOException e
) {
321 throw new ControllerExecutionException("I/O error sending 403 error",e
);
325 ScaffoldCallback callback
= new DefaultScaffoldCallback();
327 Map model
= this.scaffoldRequestHandler
.handleUpdate(request
,response
, callback
);
328 if(callback
.isInvoked()) {
329 Closure showAction
= (Closure
)controller
.getProperty(SHOW_ACTION
);
330 Map arguments
= new HashMap();
331 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ACTION
, showAction
);
332 arguments
.put( RedirectDynamicMethod
.ARGUMENT_ID
, model
.get( RedirectDynamicMethod
.ARGUMENT_ID
) );
334 return controller
.invokeMethod(RedirectDynamicMethod
.METHOD_SIGNATURE
,new Object
[]{ arguments
});
337 Map arguments
= new HashMap();
338 arguments
.put( RenderDynamicMethod
.ARGUMENT_VIEW
, EDIT_ACTION
);
339 arguments
.put( RenderDynamicMethod
.ARGUMENT_MODEL
,model
);
340 ModelAndView mv
= this.scaffoldResponseHandler
.handleResponse(request
, response
,EDIT_ACTION
,model
);
341 controller
.setProperty(ControllerDynamicMethods
.MODEL_AND_VIEW_PROPERTY
, mv
);
348 protected static Map actions
= new HashMap();
349 protected static Map actionClassToNameMap
= new HashMap();
352 actions
.put( INDEX_ACTION
, IndexAction
.class.getConstructors()[0] );
353 actionClassToNameMap
.put(IndexAction
.class, INDEX_ACTION
);
355 actions
.put( LIST_ACTION
, ListAction
.class.getConstructors()[0] );
356 actionClassToNameMap
.put(ListAction
.class, LIST_ACTION
);
358 actions
.put( SHOW_ACTION
, ShowAction
.class.getConstructors()[0] );
359 actionClassToNameMap
.put(ShowAction
.class, SHOW_ACTION
);
361 actions
.put( EDIT_ACTION
, EditAction
.class.getConstructors()[0] );
362 actionClassToNameMap
.put(EditAction
.class, EDIT_ACTION
);
364 actions
.put( DELETE_ACTION
, DeleteAction
.class.getConstructors()[0] );
365 actionClassToNameMap
.put(DeleteAction
.class, DELETE_ACTION
);
367 actions
.put( SAVE_ACTION
, SaveAction
.class.getConstructors()[0] );
368 actionClassToNameMap
.put(SaveAction
.class, SAVE_ACTION
);
370 actions
.put( UPDATE_ACTION
, UpdateAction
.class.getConstructors()[0] );
371 actionClassToNameMap
.put(UpdateAction
.class, UPDATE_ACTION
);
373 actions
.put( CREATE_ACTION
, CreateAction
.class.getConstructors()[0] );
374 actionClassToNameMap
.put(CreateAction
.class, CREATE_ACTION
);
378 public boolean supportsAction(String actionName
) {
379 return actions
.containsKey(actionName
);
382 public Closure
getAction(GroovyObject controller
,String actionName
) {
383 Constructor c
= (Constructor
)actions
.get(actionName
);
384 AbstractAction action
;
386 action
= (AbstractAction
)c
.newInstance(new Object
[]{this, controller
});
387 action
.setScaffoldRequestHandler(this.scaffoldRequestHandler
);
388 action
.setScaffoldResponseHandlerFactory(this.scaffoldResponseHandlerFactory
);
389 } catch (IllegalArgumentException e
) {
390 throw new ScaffoldingException("Illegal argument instantiating action ["+actionName
+"] for controller ["+controller
.getClass().getName()+"]: " + e
.getMessage(),e
);
391 } catch (InstantiationException e
) {
392 throw new ScaffoldingException("Error instantiating action ["+actionName
+"] for controller ["+controller
.getClass().getName()+"]: " + e
.getMessage(),e
);
393 } catch (IllegalAccessException e
) {
394 throw new ScaffoldingException("Illegal access instantiating action ["+actionName
+"] for controller ["+controller
.getClass().getName()+"]: " + e
.getMessage(),e
);
395 } catch (InvocationTargetException e
) {
396 throw new ScaffoldingException("Invocation error instantiating action ["+actionName
+"] for controller ["+controller
.getClass().getName()+"]: " + e
.getMessage(),e
);
404 * @param scaffoldRequestHandler The scaffoldRequestHandler to set.
406 public void setScaffoldRequestHandler(
407 ScaffoldRequestHandler scaffoldRequestHandler
) {
408 this.scaffoldRequestHandler
= scaffoldRequestHandler
;
413 * @param scaffoldResponseHandlerFactory The scaffoldResponseHandlerFactory to set.
415 public void setScaffoldResponseHandlerFactory(
416 ScaffoldResponseHandlerFactory scaffoldResponseHandlerFactory
) {
417 this.scaffoldResponseHandlerFactory
= scaffoldResponseHandlerFactory
;
420 public String
[] getSupportedActionNames() {
424 public String
getActionName(Closure action
) {
425 return (String
)actionClassToNameMap
.get(action
.getClass());
428 public ScaffoldRequestHandler
getScaffoldRequestHandler() {
429 return this.scaffoldRequestHandler
;