Permalink
Browse files

another round of refactoring to optimize code and clean it up. add te…

…sts for Joda
  • Loading branch information...
1 parent ba0e345 commit c29bcf4ffdbd44ca4f2af3e858947c16ad957050 Joshua Burnett committed Jan 5, 2012
Showing with 2,886 additions and 401 deletions.
  1. +15 −18 AuditTrailGrailsPlugin.groovy
  2. +138 −0 README.md
  3. +6 −5 grails-app/conf/Config.groovy
  4. +0 −134 grails-app/controllers/LoginController.groovy
  5. +0 −12 grails-app/controllers/LogoutController.groovy
  6. +10 −0 grails-app/domain/nine/tests/ChildDom.groovy
  7. +12 −2 grails-app/domain/nine/tests/TestDomain.groovy
  8. +13 −0 grails-app/domain/nine/tests/ValDirty.groovy
  9. +10 −0 src/groovy/nine/tests/SuperDom.groovy
  10. +110 −0 src/groovy/nineci/hibernate/AuditTrailHelper.groovy
  11. +36 −75 src/groovy/nineci/hibernate/AuditTrailInterceptor.groovy
  12. +0 −24 src/groovy/nineci/hibernate/NewObjectIdGenerator.groovy
  13. +93 −58 src/java/gorm/AuditStampASTTransformation.java
  14. +79 −0 src/java/gorm/FieldProps.java
  15. +0 −29 src/java/gorm/NewFieldProps.java
  16. +17 −7 test/integration/nine/tests/AuditStampTests.groovy
  17. +27 −0 test/integration/nine/tests/ChildDomTests.groovy
  18. +0 −35 test/integration/nine/tests/IdGeneratorTests.groovy
  19. +35 −0 test/integration/nine/tests/ValDirtyTests.groovy
  20. +8 −0 test/projects/joda/.gitignore
  21. +7 −0 test/projects/joda/application.properties
  22. +5 −0 test/projects/joda/grails-app/conf/ApplicationResources.groovy
  23. +7 −0 test/projects/joda/grails-app/conf/BootStrap.groovy
  24. +49 −0 test/projects/joda/grails-app/conf/BuildConfig.groovy
  25. +108 −0 test/projects/joda/grails-app/conf/Config.groovy
  26. +43 −0 test/projects/joda/grails-app/conf/DataSource.groovy
  27. +13 −0 test/projects/joda/grails-app/conf/UrlMappings.groovy
  28. +3 −0 test/projects/joda/grails-app/conf/spring/resources.groovy
  29. +8 −0 test/projects/joda/grails-app/domain/joda/AuditThis.groovy
  30. +55 −0 test/projects/joda/grails-app/i18n/messages.properties
  31. +11 −0 test/projects/joda/grails-app/views/error.gsp
  32. +116 −0 test/projects/joda/grails-app/views/index.gsp
  33. +28 −0 test/projects/joda/grails-app/views/layouts/main.gsp
  34. +31 −0 test/projects/joda/test/integration/joda/AuditThisTests.groovy
  35. +27 −0 test/projects/joda/test/unit/joda/AuditUnitTests.groovy
  36. +33 −0 test/projects/joda/web-app/WEB-INF/applicationContext.xml
  37. +14 −0 test/projects/joda/web-app/WEB-INF/sitemesh.xml
  38. +550 −0 test/projects/joda/web-app/WEB-INF/tld/grails.tld
  39. +311 −0 test/projects/joda/web-app/WEB-INF/tld/spring.tld
  40. +109 −0 test/projects/joda/web-app/css/errors.css
  41. +585 −0 test/projects/joda/web-app/css/main.css
  42. +82 −0 test/projects/joda/web-app/css/mobile.css
  43. +25 −0 test/unit/nine/tests/AstTests.groovy
  44. +48 −0 test/unit/nine/tests/FieldPropsTests.groovy
  45. +9 −2 test/unit/nine/tests/TestDomainTests.groovy
@@ -1,8 +1,9 @@
+import gorm.*
class AuditTrailGrailsPlugin {
// the plugin version
- def version = "1.2.1"
- def grailsVersion = "1.3 > *"
+ def version = "2.0.0"
+ def grailsVersion = "1.3.6 > *"
def author = "Joshua Burnett"
def authorEmail = "joshua@greenbill.com"
@@ -20,6 +21,7 @@ class AuditTrailGrailsPlugin {
'grails-app/domain/**',
'grails-app/controllers/**',
'grails-app/conf/*Config*',
+ 'src/groovy/nine/tests/**',
"web-app/**/*"
]
@@ -28,16 +30,19 @@ class AuditTrailGrailsPlugin {
def doWithSpring = {
def cfg = application.config.grails.plugin.audittrail
- //eventTriggeringInterceptor(AuditStampInterceptor)
- entityInterceptor(nineci.hibernate.AuditTrailInterceptor){
+ def fprops = FieldProps.buildFieldMap(application.config)
+
+ auditTrailHelper(nineci.hibernate.AuditTrailHelper){
grailsApplication = ref("grailsApplication")
- createdByField = cfg.createdBy.field?:null
- editedByField = cfg.editedBy.field?:null
- editedDateField = cfg.editedDate.field?:null
- createdDateField = cfg.createdDate.field?:null
+ fieldPropsMap = fprops
companyIdField = cfg.companyId.field?:null
- currentUserClosure = cfg.currentUserClosure?:null
- }
+ }
+
+ entityInterceptor(nineci.hibernate.AuditTrailInterceptor){
+ auditTrailHelper = ref("auditTrailHelper")
+ fieldPropsMap = fprops
+ }
+
}
def doWithDynamicMethods = { ctx ->
@@ -52,13 +57,5 @@ class AuditTrailGrailsPlugin {
}
- def getFieldNames(application){
- def cfg = application.config.grails.plugin.audittrail
- //try old way
- if(!cfg){
- cfg = application.config.stamp.audit
- }
- return cfg.flatten()
- }
}
View
@@ -0,0 +1,138 @@
+# Overview
+
+This plugin lets you add an annotation to your domain classes so the necessary created/updated audit fields will get added. On save() the domain will get "stamped" after a new insert or update. This eliminates the need for setting up a base class.
+It will automatically add fields based on your settings in Config.groovy.
+Provides an AST transformation annotation and hibernate events to take care of "stamping" for your gorm objects with the user who edited and/or created it as well as the edited and created dates.
+
+# Goals
+
+* DRY - setup config and then a single @gorm.AuditStamp on your domain will give the fields
+* Eliminate the need for a base class to store audit fields
+* Provide more the ability to configure the names of the date and user fields. It was a big break in our standard to use "dateCreated" and "lastUpdated"
+* Keep the nullable:false constraint on the audit fields with the ability to configure and override it if need be.
+* work with Joda or with normal Date
+
+# Using the @gorm.AuditStamp annotation
+
+Add to your config.groovy each field you want added
+
+grails{
+ plugin{
+ audittrail{
+ createdBy.field = "createdBy" //add whatever names you want used for the
+ editedBy.field = "editedBy"
+ createdDate.field = "createdDate"
+ editedDate.field = "editedDate"
+ }
+ }
+}
+
+Add the annotation to your domain class
+
+ @gorm.AuditStamp
+ class Note{
+ String note
+ }
+
+During compile time the AST transformation will add fields just as if you wrote your domain like so:
+
+ class Note{
+ String note
+
+ Long createdBy
+ Long editedBy
+ Date editedDate
+ Date createdDate
+
+ static constaints = {
+ createdBy nullable:false,display:false,editable:false
+ editedBy nullable:false,display:false,editable:false
+ editedDate nullable:false,display:false,editable:false
+ createdDate nullable:false,display:false,editable:false
+ }
+
+ def beforeValidate() { //if this already existed then it just append the code
+ //this sets the fields if this is a new (about to be inserted) instance
+ ...applicationContext.getBean('auditTrailHelper').initializeFields(this)
+ }
+
+ }
+
+## No annotation
+
+The annotation is just an AST transformation as a convenience. You can add the fields manually to your domains that match whats you have configured in config.grooy and the events will fire on those fields. This includes other hibernate/java entities.
+It uses the AuditTrailInterceptor to stamp the fields on the hibernate objects if they exists.
+
+# Events and the interceptor
+
+As seen in the above example, this allows you to keep your fields set to "nullable:false" since this annotation will add/append code to the beforeValidate() to make sure the fields are initialized properly. It also setups
+
+## Security
+
+The plugin defaults to using Spring Security but it is not dependent on it. If no currentUserClosure
+
+# Configuration Options
+
+the following show the options and defaults. For a field to be added by the annotation at least on config setting needs to be present.
+
+ grails{
+ plugin{
+ audittrail{
+ // ** if field is not specified then it will default to 'createdBy'
+ createdBy.field = "createdBy" // createdBy is default
+ // ** fully qualified class name for the type
+ createdBy.type = "java.lang.Long" //Long is the default
+ // ** the constraints settings
+ createdBy.constraints = "nullable:false,display:false,editable:false"
+ // ** the mapping you want setup
+ createdBy.mapping = "column: 'inserted_by'" //<-example as there are NO defaults for mapping
+
+ createdDate.field = "createdDate"
+ createdDate.type = "java.util.DateTime"
+ createdDate.constraints = "nullable:false,display:false,editable:false"
+ createdDate.mapping = "column: 'date_created'" //<-example as there are NO defaults for mapping
+
+ etc.....
+
+ //custom closure to return the current user who is logged in
+ currentUserClosure = {ctx->
+ //ctx is the applicationContext
+ //default is basically
+ return springSecurityService.principal?.id
+ }
+ //there are NO defaults for companyId.
+ companyId.field = "companyId" //used for multi-tenant apps and is just the name of the field to use
+ }
+
+## Joda Time Example
+
+this also shows how you can set your own currentUserClosure for stamping the user fields
+
+ grails{
+ plugin{
+ audittrail{
+ createdBy.type = "java.lang.String"
+
+ editedBy.type = "java.lang.String"
+
+ createdDate.type = "org.joda.time.DateTime"
+ createdDate.mapping = "type: org.jadira.usertype.dateandtime.joda.PersistentDateTime"
+
+ editedDate.type = "org.joda.time.DateTime"
+ editedDate.mapping = "type: org.jadira.usertype.dateandtime.joda.PersistentDateTime"
+
+ currentUserClosure = {ctx->
+ return ctx.mySecurityService.currentUserLogin()
+ }
+ }
+ }
+ }
+
+# changes from 1.2 -> 2.0 (many are breaking)
+
+* defaults on the added fields are now to set nullable:true and not have default values
+* changed the name space in config from stamp.audit to grails.plugin.audittrail
+* major config overhall so you can set types,constraints etc for each audit field
+* there is now an ability to set your own currrentUserClosure and the dependency on SpringSecurity is gone.
+
+
@@ -26,7 +26,7 @@ import static grails.util.Environment.*
grails.gorm.default.mapping = {
- id column: 'OID', generator:'nineci.hibernate.NewObjectIdGenerator'
+ id column: 'OID', generator:'native'
}
// Added by the Spring Security Core plugin:
@@ -41,15 +41,16 @@ grails{
createdBy.type = "java.lang.Long" //fully qualified class name if not a java.lang.(String,Long,etc..)
createdDate{
- field = "createdDate"
- type = "java.util.Date"
- constraints = "nullable:true"
- mapping = "column: 'date_created'"
+ field = "createdDate" //
+ type = "java.util.Date" //the class name type
}
//Will try a joda time on this one
editedDate.field = "editedDate"//date edited
editedBy.field = "updatedBy" //id who updated/edited
+ editedBy.type = "java.lang.Long" //fully qualified class name if not a java.lang.(String,Long,etc..)
+ editedBy.constraints = "nullable:true, max:90000l"
+ editedBy.mapping = "column: 'whoUpdated'"
companyId.field = "companyId" //used for multi-tenant apps
}
@@ -1,134 +0,0 @@
-import grails.converters.JSON
-
-import javax.servlet.http.HttpServletResponse
-
-import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
-
-import org.springframework.security.authentication.AccountExpiredException
-import org.springframework.security.authentication.CredentialsExpiredException
-import org.springframework.security.authentication.DisabledException
-import org.springframework.security.authentication.LockedException
-import org.springframework.security.core.context.SecurityContextHolder as SCH
-import org.springframework.security.web.WebAttributes
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
-
-class LoginController {
-
- /**
- * Dependency injection for the authenticationTrustResolver.
- */
- def authenticationTrustResolver
-
- /**
- * Dependency injection for the springSecurityService.
- */
- def springSecurityService
-
- /**
- * Default action; redirects to 'defaultTargetUrl' if logged in, /login/auth otherwise.
- */
- def index = {
- if (springSecurityService.isLoggedIn()) {
- redirect uri: SpringSecurityUtils.securityConfig.successHandler.defaultTargetUrl
- }
- else {
- redirect action: 'auth', params: params
- }
- }
-
- /**
- * Show the login page.
- */
- def auth = {
-
- def config = SpringSecurityUtils.securityConfig
-
- if (springSecurityService.isLoggedIn()) {
- redirect uri: config.successHandler.defaultTargetUrl
- return
- }
-
- String view = 'auth'
- String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
- render view: view, model: [postUrl: postUrl,
- rememberMeParameter: config.rememberMe.parameter]
- }
-
- /**
- * The redirect action for Ajax requests.
- */
- def authAjax = {
- response.setHeader 'Location', SpringSecurityUtils.securityConfig.auth.ajaxLoginFormUrl
- response.sendError HttpServletResponse.SC_UNAUTHORIZED
- }
-
- /**
- * Show denied page.
- */
- def denied = {
- if (springSecurityService.isLoggedIn() &&
- authenticationTrustResolver.isRememberMe(SCH.context?.authentication)) {
- // have cookie but the page is guarded with IS_AUTHENTICATED_FULLY
- redirect action: 'full', params: params
- }
- }
-
- /**
- * Login page for users with a remember-me cookie but accessing a IS_AUTHENTICATED_FULLY page.
- */
- def full = {
- def config = SpringSecurityUtils.securityConfig
- render view: 'auth', params: params,
- model: [hasCookie: authenticationTrustResolver.isRememberMe(SCH.context?.authentication),
- postUrl: "${request.contextPath}${config.apf.filterProcessesUrl}"]
- }
-
- /**
- * Callback after a failed login. Redirects to the auth page with a warning message.
- */
- def authfail = {
-
- def username = session[UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY]
- String msg = ''
- def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
- if (exception) {
- if (exception instanceof AccountExpiredException) {
- msg = g.message(code: "springSecurity.errors.login.expired")
- }
- else if (exception instanceof CredentialsExpiredException) {
- msg = g.message(code: "springSecurity.errors.login.passwordExpired")
- }
- else if (exception instanceof DisabledException) {
- msg = g.message(code: "springSecurity.errors.login.disabled")
- }
- else if (exception instanceof LockedException) {
- msg = g.message(code: "springSecurity.errors.login.locked")
- }
- else {
- msg = g.message(code: "springSecurity.errors.login.fail")
- }
- }
-
- if (springSecurityService.isAjax(request)) {
- render([error: msg] as JSON)
- }
- else {
- flash.message = msg
- redirect action: 'auth', params: params
- }
- }
-
- /**
- * The Ajax success redirect url.
- */
- def ajaxSuccess = {
- render([success: true, username: springSecurityService.authentication.name] as JSON)
- }
-
- /**
- * The Ajax denied redirect url.
- */
- def ajaxDenied = {
- render([error: 'access denied'] as JSON)
- }
-}
@@ -1,12 +0,0 @@
-import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
-
-class LogoutController {
-
- /**
- * Index action. Redirects to the Spring security logout uri.
- */
- def index = {
- // TODO put any pre-logout code here
- redirect uri: SpringSecurityUtils.securityConfig.logout.filterProcessesUrl // '/j_spring_security_logout'
- }
-}
@@ -0,0 +1,10 @@
+package nine.tests
+
+class ChildDom extends SuperDom{
+ String childProp
+
+ static constraints = {
+ childProp nullable:false, blank:false
+ }
+
+}
Oops, something went wrong.

0 comments on commit c29bcf4

Please sign in to comment.