I’ve recently been presented with several scenarios while developing under Grails that require application workflow decisions, and the most logical place to put these decisions has been inside the SecurityFilters class. The SecurityFilters class is extremely handy for doing things like redirecting based off of user role, and handling customer authentication, etc… But I’ve found a new use for it that makes the workflow dynamics of my web applications extremely streamlined.


For the purposes of this blog post, I have pieced together components of a small web application to demonstrate how we can use Annotations on a Controller’s Actions to handle the confirmation to delete an object. My hope is that in viewing the bare metal of the application that you will see the potential of action-based annotations… And how simple creating annotations for decision making inside of a Grails application can be.


There are five major components that go into making this work: the annotation class; the controller annotation helper class (this will be how we determine if an action is annotated with a specific type of annotation); the controller; the view; and the SecurityFilters.


The annotation class will really serve as just a namespace for us to determine the workflow. For this, I mean that if our goal is to have a specific action require confirmation before completing that action, we should have an annotation interface named something like “RequiresConfirmation”. If we wanted to check against the opposite, for example, we might have another class named “DoesNotRequireConfirmation,” and we might annotate specific controller actions with that. To give you the bare-metal idea, I’ve outlined how to create the annotation interface in Grails… This is dead on to how we would do this in Java:

src/groovy/com/rhcedan/annotation/RequiresConfirmation.groovy

package com.rhcedan.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
@interface RequiresConfirmation {
	// We don't need any methods since we're using
	// this strictly for filter decision-making
}





The next piece of the puzzle is the ControllerAnnotationHelper class. This will be what we call from the SecurityFilters to determine if the action that is being called is specifically annotated. The hasAnnotation() method takes three arguments: the controller class; the annotation class; and the name of the action.
src/groovy/com/rhcedan/annotation/ControllerAnnotationHelper.groovy

package com.rhcedan.annotation

class ControllerAnnotationHelper {
	static boolean hasAnnotation(Class clazz, Class annotationClass, String actionName) {
	    boolean retVal = false
	    clazz.declaredFields.each {
	        if (it.name == actionName && it.isAnnotationPresent(annotationClass)) {
	            retVal = true
	        }
	    }
	    return retVal
	}
}





The controller is pretty straight forward. For the purposes of this example, I wanted to demonstrate how we could have a “delete” method require confirmation before proceeding. To do so, I have added two additional controller actions: “confirm” and “cancel”. It should be pretty clear what these methods do. If it’s not, then… Then I don’t know what… Then, stop reading now, I guess. As you can see from the example, I have annotated the “delete” action with the RequiresConfirmation class. In the final section, I will demonstrate how this annotation gets used by the SecurityFilters class to determine the workflow of the web application (eg. redirect the user to a confirmation page before finally allowing them to continue to the deletion page).
grails-app/controllers/com/rhcedan/WebController.groovy

package com.rhcedan

class WebController {

	@RequiresConfirmation
	def delete = {
		// reset for next time
		session.confirmed = 0

		def webInstance = Web.get(params.id)
		if (webInstance) {
			webInstance.delete(flush: true)
			flash.message = "${message(code: 'default.deleted.message', args: [message(code: 'web.label', default: 'Web'), params.id])}"
			redirect(action: "list")
		}
	}

	def confirm = {
		render(view:'confirm', controller:'web', model:[forwardTo:params.forwardTo, msg: params.msg])
		session.confirmed = 1
	}

	def cancel = {
		session.confirmed = 0
		redirect(action:'list')
	}
}





I’ve done two things with the view layer to make them relatively dynamic and reusable as we add different annotations and different annotate-able actions in the future. From the controller above, you can see that the view layer is forwarded two specific parameters that will be handled in its rendering. One will be the “forwardTo” URI; this is the URI that was originally requested before we forwarded the user to the confirmation page. We use this data to perform the “confirmation” piece of the action. For the purposes of this example, the forwardTo URI will store the link to the “delete” action, including all applicable parameters, and they will be rendered inside of the GSP. In the “delete” action inside of the controller, there is a session variable that is set and unset during the confirmation->completion phase to manage the worflow. The second component that I have included is the ability to supply (from the SecurityFilters) a dynamic message that will be rendered to the user when they reach the confirmation page. For example, if you are annotating both a “delete” action and an “update” action with the same confirmation, you can change the strings according to the workflow.
grails-app/views/web/confirm.gsp

	${msg}
	<br/><br/>
	<a href="${forwardTo}">
		Confirm
	</a>
	<br/>
	<g:link controller="web"
			action="cancel">
		Cancel
	</g:link>





And, the final piece to this puzzle is the SecurityFilters class. There are three parts to determining workflow dynamics from the SecurityFilters: the controller/action combination; if the action is annotated with a specific class; and if the control mechanism — in this case, the “confirmed” session variable to determine if we’ve already reached the confirmation page and confirmed that we want to proceed — has been toggled appropriately. Satisfaction of these three cases will redirect the user to the confirmation page, allow them to confirm that they want to proceed, and then finally proceed to their originally intended step.
grails-app/conf/SecurityFilters.groovy


import com.rhcedan.annotation.ControllerAnnotationHelper as CAH
import com.rhcedan.annotation.RequiresConfirmation
import com.rhcedan.WebController

class SecurityFilters {
	def filters = {
		confirmDelete(controller:'web', action:'delete') {
			before = {
				if (CAH.hasAnnotation(WebController.class, RequiresConfirmation.class, 'delete') && !session.confirmed) {
					redirect(action:'confirm', controller:'web', params:[forwardTo:request.forwardURI, msg: 'Are you sure you want to delete this??'])
				}
			}
		}
	}
}



I hope that this is helpful for somebody — it has already proven immensely useful for me. As always, if you have any questions, please do not hesitate to comment or email me directly at dan@rhcedan.com.

Thanks


Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2013 Dan's Blog Suffusion theme by Sayontan Sinha