Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

…sts for Joda
  • Loading branch information...
commit c29bcf4ffdbd44ca4f2af3e858947c16ad957050 1 parent ba0e345
Joshua Burnett authored
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
View
33 AuditTrailGrailsPlugin.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
138 README.md
@@ -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.
+
+
View
11 grails-app/conf/Config.groovy
@@ -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
}
View
134 grails-app/controllers/LoginController.groovy
@@ -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)
- }
-}
View
12 grails-app/controllers/LogoutController.groovy
@@ -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'
- }
-}
View
10 grails-app/domain/nine/tests/ChildDom.groovy
@@ -0,0 +1,10 @@
+package nine.tests
+
+class ChildDom extends SuperDom{
+ String childProp
+
+ static constraints = {
+ childProp nullable:false, blank:false
+ }
+
+}
View
14 grails-app/domain/nine/tests/TestDomain.groovy
@@ -9,7 +9,17 @@ class TestDomain {
Long companyId = 0
- static mapping={ table 'TestDomains' } //note you have to declare this or AuditStamp won't add anything
+ static mapping={
+ table 'TestDomains'
+ }
+
+ // def beforeValidate() {
+ // println 'in validate'
+ // }
+/* static constraints = { ->
+ createdDate nullable:true
+ }*/
- static constraints = { } //note you have to declare this or AuditStamp won't add anything
+
+
}
View
13 grails-app/domain/nine/tests/ValDirty.groovy
@@ -0,0 +1,13 @@
+package nine.tests
+
+import gorm.AuditStamp;
+
+class ValDirty {
+
+ String name
+ //Date dt = new Date()
+
+ def beforeValidate(List blah) {
+ println 'ValDirty beforeValidate'
+ }
+}
View
10 src/groovy/nine/tests/SuperDom.groovy
@@ -0,0 +1,10 @@
+package nine.tests
+
+@gorm.AuditStamp
+abstract class SuperDom {
+ String superProp
+
+ static constraints = {
+ superProp nullable:true
+ }
+}
View
110 src/groovy/nineci/hibernate/AuditTrailHelper.groovy
@@ -0,0 +1,110 @@
+package nineci.hibernate //grails.plugin.audittrail
+import org.hibernate.EmptyInterceptor
+import org.hibernate.type.Type
+import org.apache.log4j.Logger
+import org.springframework.context.ApplicationContextAware
+import org.springframework.context.ApplicationContext
+import org.springframework.beans.factory.InitializingBean
+
+class AuditTrailHelper implements ApplicationContextAware,InitializingBean{
+ private static final Logger log = Logger.getLogger(AuditTrailInterceptor)
+ def currentUserClosure
+ //injected
+ def grailsApplication
+ Map fieldPropsMap
+ String companyIdField
+
+ ApplicationContext applicationContext
+
+ static Long ANONYMOUS_USER = 0
+
+ void initializeFields(Object entity) {
+ if(log.isDebugEnabled()) log.debug "in beforeValidateInit for $entity"
+ //if its not new then just exit as we will assume an updated entity is setup correctly
+ if(!isNewEntity(entity)) return
+
+ def time = System.currentTimeMillis()
+ //assume its a new entity
+ ['createdDate','editedDate','createdBy','editedBy'].each{ key->
+ def field = fieldPropsMap.get(key).name
+ def property = entity.metaClass.hasProperty(entity, field)
+ if(property) {
+ def valToSet
+ if(field == 'createdDate' || field == 'editedDate'){
+ valToSet = property.getType().newInstance([time] as Object[] )
+ }else{
+ valToSet = currentUserId()
+ }
+ entity.setProperty(field,valToSet)
+ }
+ }
+
+ if(companyIdField){
+ def property = entity.metaClass.hasProperty(entity,companyIdField)
+ if(property) {
+ def curvalue = entity.getProperty(companyIdField)
+ if(curvalue==null || curvalue==0 && isUserAuthorized() ){ //only update if its 0 or null
+ entity.setProperty(companyIdField,getCompanyId())
+ }
+ }
+ }
+ }
+
+ boolean isNewEntity(entity){
+ def session = applicationContext.sessionFactory.currentSession
+ def entry = session.persistenceContext.getEntry(entity)
+ if (!entry) {
+ return true
+ }
+ }
+
+ def currentUserId() {
+ return currentUserClosure(applicationContext)
+ }
+
+ def getSpringSecurityUser = { ctx ->
+ def authPrincipal = ctx.springSecurityService.principal
+ // Added check for error coming while creating new company
+ if(authPrincipal && authPrincipal != "anonymousUser"){
+ return authPrincipal.id
+ } else {
+ //FIXME this is not ok.
+ return 0 //fall back
+ }
+ }
+
+ Boolean isUserAuthorized(){
+ def authPrincipal = applicationContext.springSecurityService.principal
+ if(authPrincipal && authPrincipal != "anonymousUser"){
+ return true
+ }else{
+ return false
+ }
+ }
+
+ Long getCompanyId() {
+ def authPrincipal = applicationContext.springSecurityService.principal
+ if(authPrincipal.hasProperty(companyIdField)){
+ return authPrincipal.companyId
+ }else{
+ //FIXME this should not return a 0 I don't think
+ return 0
+ }
+ }
+
+ //---------------------------------------------------------------------
+ // Implementation of InitializingBean interface
+ //---------------------------------------------------------------------
+
+ public void afterPropertiesSet() throws Exception {
+
+ def cfgClosure = grailsApplication.config.grails.plugin.audittrail.currentUserClosure
+ if(cfgClosure){
+ currentUserClosure = cfgClosure
+ }else{
+ currentUserClosure = getSpringSecurityUser
+ }
+
+ }
+}
+
View
111 src/groovy/nineci/hibernate/AuditTrailInterceptor.groovy
@@ -4,107 +4,68 @@ import org.hibernate.type.Type
import org.apache.log4j.Logger
import org.springframework.context.ApplicationContextAware
import org.springframework.context.ApplicationContext
+import org.springframework.beans.factory.InitializingBean
+import org.apache.commons.lang.ArrayUtils
-class AuditTrailInterceptor extends EmptyInterceptor implements ApplicationContextAware{
+class AuditTrailInterceptor extends EmptyInterceptor {
private static final Logger log = Logger.getLogger(AuditTrailInterceptor)
+
//injected
- def grailsApplication
- def currentUserClosure
- String createdByField
- String editedByField
- String editedDateField
- String createdDateField
- String companyIdField
-
- ApplicationContext applicationContext
-
- static Long ANONYMOUS_USER = 0
+ AuditTrailHelper auditTrailHelper
+ Map fieldPropsMap
boolean onFlushDirty(Object entity, Serializable id, Object[] currentState,Object[] previousState, String[] propertyNames,Type[] types) {
- def metaClass = entity.metaClass
- MetaProperty property = metaClass.hasProperty(entity, editedDateField)
- List fieldList = propertyNames.toList()
-
+
+ String field = fieldPropsMap.get("editedDate").name
+ MetaProperty property = entity.metaClass.hasProperty(entity, field)
if(property) {
def now = property.getType().newInstance([System.currentTimeMillis()] as Object[] )
- setValue(currentState, fieldList, editedDateField, now)
+ setValue(currentState, propertyNames, fieldPropsMap.get("editedDate").name, now)
}
- property = metaClass.hasProperty(entity,editedByField)
+ field = fieldPropsMap.get("editedBy").name
+ property = entity.metaClass.hasProperty(entity,field)
if(property) {
- setValue(currentState, fieldList, editedByField, getUserID())
+ setValue(currentState, propertyNames, field, auditTrailHelper.currentUserId())
}
return true
}
boolean onSave(Object entity, Serializable id, Object[] state,String[] propertyNames, Type[] types) {
- def metaClass = entity.metaClass
- MetaProperty property = metaClass.hasProperty(entity, createdDateField)
def time = System.currentTimeMillis()
- List fieldList = propertyNames.toList()
- def userId = getUserID()
- if(property) {
- def now = property.getType().newInstance([time] as Object[] )
- setValue(state, fieldList, createdDateField, now)
- }
- property = metaClass.hasProperty(entity,editedDateField)
- if(property) {
- def now = property.getType().newInstance([time] as Object[] )
- setValue(state, fieldList, editedDateField, now)
- }
- property = metaClass.hasProperty(entity,editedByField)
- if(property) {
- setValue(state, fieldList, editedByField, userId)
- }
- property = metaClass.hasProperty(entity,createdByField)
- if(property) {
- setValue(state, fieldList, createdByField, userId)
+ ['createdDate','editedDate','createdBy','editedBy'].each{ key->
+ def field = fieldPropsMap.get(key).name
+ def property = entity.metaClass.hasProperty(entity, field)
+ if(property) {
+ def valToSet
+ if(field == 'createdDate' || field == 'editedDate'){
+ valToSet = property.getType().newInstance([time] as Object[] )
+ }else{
+ valToSet = auditTrailHelper.currentUserId()
+ }
+ setValue(state, propertyNames, field, valToSet)
+ }
}
- property = metaClass.hasProperty(entity,companyIdField)
-
- if(property) {
- def curvalue = entity."$companyIdField"
- if(curvalue==null || curvalue==0 && userGoodForCompanyId() ){ //only update if its 0 or null
- setValue(state, fieldList, companyIdField, getCompanyId())
+
+ String companyIdField = auditTrailHelper.companyIdField
+ if(companyIdField){
+ def property = entity.metaClass.hasProperty(entity,companyIdField)
+ if(property) {
+ def curvalue = entity."$companyIdField"
+ if(curvalue==null || curvalue==0 && auditTrailHelper.userGoodForCompanyId() ){ //only update if its 0 or null
+ setValue(state, propertyNames, companyIdField, auditTrailHelper.getCompanyId())
+ }
}
}
return true
}
- def setValue(Object[] currentState, List fieldList, String propertyToSet, Object value) {
- int index = fieldList.indexOf(propertyToSet)
+ def setValue(Object[] currentState, String[] propertyNames, String propertyToSet, Object value) {
+ int index = ArrayUtils.indexOf(propertyNames, propertyToSet) //fieldList.indexOf(propertyToSet)
if (index >= 0) {
currentState[index] = value
}
}
- def getUserID() {
- def userClos = currentUserClosure?:getSpringSecurityUser
- return userClos(applicationContext)
- }
-
- def getSpringSecurityUser = { ctx ->
- def authPrincipal = ctx.springSecurityService.principal
- // Added check for error coming while creating new company
- if(authPrincipal && authPrincipal != "anonymousUser"){
- return authPrincipal.id
- } else {
- return 0 //fall back
- }
- }
-
- def userGoodForCompanyId(){
- def authPrincipal = applicationContext.springSecurityService.principal
- if(authPrincipal && authPrincipal != "anonymousUser"){
- return true
- }else{
- return false
- }
- }
-
- Long getCompanyId() {
- def authPrincipal = applicationContext.springSecurityService.principal
- return authPrincipal.hasProperty(companyIdField)?authPrincipal.companyId:0
- }
}
View
24 src/groovy/nineci/hibernate/NewObjectIdGenerator.groovy
@@ -1,24 +0,0 @@
-package nineci.hibernate;
-
-import java.util.Properties;
-import org.hibernate.MappingException;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.id.enhanced.TableGenerator;
-import org.hibernate.type.Type;
-
-public class NewObjectIdGenerator extends TableGenerator {
-
- @Override
- public void configure(final Type type, final Properties params, final Dialect dialect) throws MappingException {
- params.put("optimizer", "pooled");
- params.put("initial_value", "10000");
- params.put("increment_size", "100");
- params.put("prefer_entity_table_as_segment_value", "true");
- params.put("segment_value", params.getProperty( TABLE ) + ".OID");
- params.put("table_name", "NewObjectId");
- params.put("value_column_name", "NextId");
- params.put("segment_column_name", "KeyName");
- super.configure(type, params, dialect);
- }
-
-}
View
151 src/java/gorm/AuditStampASTTransformation.java
@@ -13,9 +13,7 @@
import java.util.Map;
import java.util.Properties;
-import org.codehaus.groovy.ast.ASTNode;
-import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
import org.codehaus.groovy.ast.expr.ClosureExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
@@ -32,7 +30,12 @@
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;
import org.codehaus.groovy.ast.builder.AstBuilder;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+// import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+// import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.codehaus.groovy.ast.MethodNode.*;
/**
* Performs an ast transformation on a class - adds createdBy/createdDate editedBy/EditedDate id and table
@@ -43,79 +46,103 @@
//private static final Log LOG = LogFactory.getLog(AuditStampASTTransformation.class);
private static final ConfigObject CO = new ConfigSlurper().parse(getContents(new File("./grails-app/conf/Config.groovy")));
- private static final Properties CONF = (new ConfigSlurper().parse(getContents(new File("./grails-app/conf/Config.groovy")))).toProperties();
public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {
//System.out.println("1. ConfigObject : " + CO);
- Map configMap = (Map)CO.flatten();
-
- NewFieldProps createdByField = NewFieldProps.init("createdBy","java.lang.Long",configMap);
- NewFieldProps editedByField = NewFieldProps.init("editedBy","java.lang.Long",configMap);
-
- NewFieldProps editedDateField = NewFieldProps.init("editedDate","java.util.Date",configMap);
- NewFieldProps createdDateField = NewFieldProps.init("createdDate","java.util.Date",configMap);
+ Map<String, FieldProps> fprops = FieldProps.buildFieldMap(CO);
for (ASTNode astNode : astNodes) {
if (astNode instanceof ClassNode) {
ClassNode classNode = (ClassNode) astNode;
- List<FieldNode> fnlist = classNode.getFields();
- //LOG.info("[Audit stamp ASTTransformation] Adding propertie [edited..created] to class [" + classNode.getName() + "]");
- //System.out.println(classNode.getName() + " - [Audit stamp ASTTransformation] Adding propertie [edited..created]");
- if(editedByField!=null){
- //addFieldNode(classNode,editedByField);
- classNode.addProperty(editedByField.name, Modifier.PUBLIC, new ClassNode(editedByField.type), new ConstantExpression(0), null, null);
- }
- if(createdByField!=null){
- classNode.addProperty(createdByField.name, Modifier.PUBLIC, new ClassNode(createdByField.type), new ConstantExpression(0), null, null);
- }
-
- if(editedDateField!=null){
- Expression enow = new ConstructorCallExpression(new ClassNode(editedDateField.type),MethodCallExpression.NO_ARGUMENTS);
- classNode.addProperty(editedDateField.name, Modifier.PUBLIC, new ClassNode(editedDateField.type), enow, null, null);
- addNullableConstraint(classNode,editedDateField.name);
- }
- if(createdDateField!=null){
- Expression cnow = new ConstructorCallExpression(new ClassNode(createdDateField.type),MethodCallExpression.NO_ARGUMENTS);
- classNode.addProperty(createdDateField.name, Modifier.PUBLIC, new ClassNode(createdDateField.type), cnow, null, null);
- addNullableConstraint(classNode,createdDateField.name);
- }
-
+ doBeforeValidate(classNode);
+ //debugFieldNodes(classNode);
+ //List<FieldNode> fnlist = classNode.getFields();
+
+ createUserField( classNode, fprops.get("editedBy"));
+ createUserField( classNode, fprops.get("createdBy"));
+
+ createDateField( classNode, fprops.get("editedDate"));
+ createDateField( classNode, fprops.get("createdDate"));
}
}
}
-
- public void addFieldNode(ClassNode classNode,String fieldName){
- String checkVersion = "Long " + fieldName + " = 0";
- List<ASTNode> nodes = new AstBuilder().buildFromString(CompilePhase.CLASS_GENERATION,false,checkVersion);
- FieldNode fnode = (FieldNode)nodes.get(0);
- classNode.addField(fnode);
+
+ public void doBeforeValidate(ClassNode classNode){
+ MethodNode mn = classNode.getMethod("beforeValidate", Parameter.EMPTY_ARRAY);
+ if(mn == null){
+ classNode.addMethod("beforeValidate", Modifier.PUBLIC, ClassHelper.OBJECT_TYPE, Parameter.EMPTY_ARRAY, null, new BlockStatement());
+ mn = classNode.getMethod("beforeValidate", Parameter.EMPTY_ARRAY);
+ assert mn != null;
+ }
+ // System.out.println(mn.toString());
+ // System.out.println(mn.getCode());
+ String configStr = "org.codehaus.groovy.grails.commons.ApplicationHolder.application.mainContext" +
+ ".getBean('auditTrailHelper').initializeFields(this)";
+
+ BlockStatement newConfig = (BlockStatement) new AstBuilder().buildFromString(configStr).get(0);
+
+ ReturnStatement returnStatement = (ReturnStatement) newConfig.getStatements().get(0);
+ ExpressionStatement exStatment = new ExpressionStatement(returnStatement.getExpression());
+ BlockStatement block = (BlockStatement) mn.getCode();
+ block.addStatement(exStatment);
+ //System.out.println(block);
+ }
+
+ public void createUserField(ClassNode classNode,FieldProps fieldProps){
+ if(fieldProps==null) return;
+ //ConstantExpression cce = (fieldProps.initValue!=null) ? new ConstantExpression(fieldProps.initValue) : null;
+ classNode.addProperty(fieldProps.name, Modifier.PUBLIC, new ClassNode(fieldProps.type), null, null, null);
+ addSettings("mapping",classNode,fieldProps.name,fieldProps.mapping);
+ addSettings("constraints",classNode,fieldProps.name,fieldProps.constraints);
+ }
+
+ public void createDateField(ClassNode classNode,FieldProps fieldProps){
+ if(fieldProps==null) return;
+ // Expression cnow = null;
+ // if(fieldProps.initValue == "now"){
+ // cnow = new ConstructorCallExpression(new ClassNode(fieldProps.type),MethodCallExpression.NO_ARGUMENTS);
+ // }
+ classNode.addProperty(fieldProps.name, Modifier.PUBLIC, new ClassNode(fieldProps.type), null, null, null);
+ addSettings("mapping",classNode,fieldProps.name,fieldProps.mapping);
+ addSettings("constraints",classNode,fieldProps.name,fieldProps.constraints);
}
- public void addNullableConstraint(ClassNode classNode,String fieldName){
- FieldNode closure = classNode.getDeclaredField("constraints");
-
- if(closure!=null){
-
+ public void addSettings(String name,ClassNode classNode,String fieldName,String config){
+ if(config==null)
+ return;
+
+ String configStr = fieldName + " " + config;
+
+ BlockStatement newConfig = (BlockStatement) new AstBuilder().buildFromString(configStr).get(0);
+
+ FieldNode closure = classNode.getField(name);
+ if(closure == null){
+ createStaticClosure(classNode, name);
+ closure = classNode.getField(name);
+ assert closure != null;
+ }
+
+ if(!hasFieldInClosure(closure,fieldName)){
+ ReturnStatement returnStatement = (ReturnStatement) newConfig.getStatements().get(0);
+ ExpressionStatement exStatment = new ExpressionStatement(returnStatement.getExpression());
ClosureExpression exp = (ClosureExpression)closure.getInitialExpression();
BlockStatement block = (BlockStatement) exp.getCode();
-
- if(!hasFieldInClosure(closure,fieldName)){
- NamedArgumentListExpression namedarg = new NamedArgumentListExpression();
- namedarg.addMapEntryExpression(new ConstantExpression("nullable"), new ConstantExpression(true));
- MethodCallExpression constExpr = new MethodCallExpression(
- VariableExpression.THIS_EXPRESSION,
- new ConstantExpression(fieldName),
- namedarg
- );
- block.addStatement(new ExpressionStatement(constExpr));
- //System.out.println(classNode.getName() + " - Added nullabel constraint for "+ fieldName);
- }
+ block.addStatement(exStatment);
+ //System.out.println(classNode.getName() + " - Added "+ configStr);
+ //System.out.println(block.toString());
}
- //System.out.println(block.toString());
+ assert hasFieldInClosure(closure,fieldName) == true;
}
-
+ public void createStaticClosure(ClassNode classNode,String name){
+ FieldNode field = new FieldNode(name, ACC_PUBLIC | ACC_STATIC,
+ new ClassNode(java.lang.Object.class), new ClassNode(classNode.getClass()),null);
+ ClosureExpression expr = new ClosureExpression(Parameter.EMPTY_ARRAY, new BlockStatement());
+ expr.setVariableScope(new VariableScope());
+ field.setInitialValueExpression(expr);
+ classNode.addField(field);
+ }
public boolean hasFieldInClosure(FieldNode closure, String fieldName){
if(closure != null){
@@ -134,6 +161,14 @@ public boolean hasFieldInClosure(FieldNode closure, String fieldName){
}
return false;
}
+
+ public void debugFieldNodes(ClassNode classNode){
+ //List<FieldNode> fnlist = classNode.getFields();
+ List<PropertyNode> fnlist = classNode.getProperties() ;
+ for (PropertyNode node : fnlist) {
+ System.out.println(classNode.getName() + " : " + node.getName() + "," );
+ }
+ }
static public String getContents(File aFile) {
View
79 src/java/gorm/FieldProps.java
@@ -0,0 +1,79 @@
+package gorm;
+import groovy.util.ConfigObject;
+import java.util.Map;
+import java.util.HashMap;
+
+class FieldProps {
+ private static final String DATE_CONS = "nullable:false,display:false,editable:false";
+ private static final String USER_CONS = "nullable:false,display:false,editable:false";
+
+ String name;
+ Class type;
+ //Object initValue;
+ String constraints;
+ String mapping;
+
+ public static FieldProps init(String defaultName,String defaultType, String defaultCons,String defaultMapping, ConfigObject configObj) {
+ //System.out.println("ConfigObject : " + co);
+ if(configObj == null || configObj.isEmpty()) return null;
+ Map co = (Map)configObj.flatten();
+
+
+ String baseKey = "grails.plugin.audittrail." + defaultName;
+ if(getMap(configObj, baseKey) == null){
+ return null;
+ }
+ FieldProps newField = new FieldProps();
+ newField.name = (String)co.get(baseKey + ".field");
+ if(newField.name == null){
+ newField.name = defaultName;
+ }
+ String className = (String)co.get(baseKey + ".type");
+ if(className == null || className==""){
+ className = defaultType;
+ }
+ try {
+ newField.type = Class.forName(className);
+ }catch (ClassNotFoundException e) {
+ throw new RuntimeException("Class " + className + " could not be found for audittrail setting " + defaultName);
+ }
+ if(!co.containsKey(baseKey+ ".constraints") ){
+ newField.constraints = defaultCons;
+ }else{
+ newField.constraints = (String)co.get(baseKey+ ".constraints");
+ }
+ newField.constraints = (String)co.get(baseKey+ ".constraints");
+ newField.mapping = (String)co.get(baseKey + ".mapping");
+
+ return newField;
+ }
+
+ public static Map<String, FieldProps> buildFieldMap(ConfigObject config){
+ Map<String, FieldProps> map = new HashMap<String, FieldProps>();
+ map.put("createdBy",FieldProps.init("createdBy","java.lang.Long",USER_CONS,null,config));
+ map.put("editedBy",FieldProps.init("editedBy","java.lang.Long",USER_CONS,null,config));
+
+ map.put("editedDate",FieldProps.init("editedDate","java.util.Date",DATE_CONS,null,config));
+ map.put("createdDate",FieldProps.init("createdDate","java.util.Date",DATE_CONS,null,config));
+ return map;
+ }
+
+ static public Object getMap(Map configMap, String keypath) {
+ String keys[] = keypath.split("\\.");
+ Map map = configMap;
+ for(String key : keys){
+ Object val = map.get(key);
+ if(val !=null){
+ //System.out.println("got a key for are " +key);
+ if(val instanceof Map){
+ map = (Map)map.get(key);
+ } else{
+ return val;
+ }
+ }else{
+ return null;
+ }
+ }
+ return map;
+ }
+}
View
29 src/java/gorm/NewFieldProps.java
@@ -1,29 +0,0 @@
-package gorm;
-import groovy.util.ConfigObject;
-import java.util.Map;
-
-class NewFieldProps {
-
- String name;
- Class type;
-
- public static NewFieldProps init(String defaultName,String defaultType, Map co) {
- //System.out.println("ConfigObject : " + co);
- if(co == null || co.isEmpty()) return null;
- NewFieldProps newField = new NewFieldProps();
- newField.name = (String)co.get("grails.plugin.audittrail." + defaultName + ".field");
- if(newField.name == null){
- newField.name = defaultName;
- }
- String className = (String)co.get("grails.plugin.audittrail." + defaultName + ".type");
- if(className == null || className==""){
- className = defaultType;
- }
- try {
- newField.type = Class.forName(className);
- }catch (ClassNotFoundException e) {
- throw new RuntimeException("Class " + className + " could not be found for audittrail setting " + defaultName);
- }
- return newField;
- }
-}
View
24 test/integration/nine/tests/AuditStampTests.groovy
@@ -12,12 +12,22 @@ import org.apache.commons.lang.time.DateUtils
class AuditStampTests extends BaseInt {
def sessionFactory
def dataSource
+ def grailsApplication
void setUp() {
super.setUp();
}
-
+ void test_constraints(){
+ def art = grailsApplication.getDomainClass("nine.tests.TestDomain")
+ assert art
+ assert art.constraints.editedDate.getAppliedConstraint('nullable').isNullable() == false
+ assert art.constraints.createdDate.getAppliedConstraint('nullable').isNullable() == false
+ assert art.constraints.updatedBy.getAppliedConstraint('nullable').isNullable() == true
+ assert art.constraints.updatedBy.getAppliedConstraint('max').maxValue == 90000l
+ //def prop= art.getPropertyByName("updatedBy")
+ }
+
void testCreateEditInsert() {
def dom = new TestDomain(name:"blah")
if( !dom.save(flush:true) ) {
@@ -27,7 +37,7 @@ class AuditStampTests extends BaseInt {
}
assertNotNull(dom.id);
def sql = new Sql(dataSource);
- def sqlCall = 'select oid, companyId,createdBy, createdDate, updatedBy, editedDate from TestDomains where oid = ' + dom.id
+ def sqlCall = 'select oid, companyId,createdBy, createdDate, whoUpdated, editedDate from TestDomains where oid = ' + dom.id
println sqlCall
//def data = hibSession.createSQLQuery(sqlCall).uniqueResult();
def data = sql.firstRow(sqlCall)
@@ -38,7 +48,7 @@ class AuditStampTests extends BaseInt {
assertNotNull(data.editedDate)
assertTrue DateUtils.isSameDay(data.createdDate, new Date())
assertTrue DateUtils.isSameDay(data.editedDate, new Date())
- assertEquals(authUser.id, data.updatedBy)
+ assertEquals(authUser.id, data.whoUpdated)
assertEquals(authUser.id, data.createdBy)
}
@@ -47,7 +57,7 @@ class AuditStampTests extends BaseInt {
assert dom.save(flush:true)
def sql = new Sql(dataSource);
- def sqlCall = 'select oid, companyId,createdBy, createdDate, updatedBy, editedDate from TestDomains where oid = ' + dom.id
+ def sqlCall = 'select oid, companyId,createdBy, createdDate, whoUpdated, editedDate from TestDomains where oid = ' + dom.id
def data = sql.firstRow(sqlCall)
assertNotNull(data)
@@ -60,7 +70,7 @@ class AuditStampTests extends BaseInt {
java.sql.Date yesterdaySQL = new java.sql.Date(yesterday.getTime())
def sql = new Sql(sessionFactory.getCurrentSession().connection())
- sql.execute("insert into TestDomains (oid,version,companyId,name, createdBy, createdDate, updatedBy, editedDate) "+
+ sql.execute("insert into TestDomains (oid,version,companyId,name, createdBy, createdDate, whoUpdated, editedDate) "+
" values (?,?,?,?,?,?,?,?)", [2,0,5,"xxx", 0,yesterdaySQL,0,yesterdaySQL])
@@ -73,14 +83,14 @@ class AuditStampTests extends BaseInt {
}
}
- def sqlCall = 'select oid, createdBy, createdDate, updatedBy, editedDate from TestDomains where oid = ' + dom.id
+ def sqlCall = 'select oid, createdBy, createdDate, whoUpdated, editedDate from TestDomains where oid = ' + dom.id
println sqlCall
def data = sql.firstRow(sqlCall)
assertNotNull(data)
assertEquals(dom.id, data.oid)
assertNotNull(data.editedDate)
assertTrue DateUtils.isSameDay(data.editedDate, new Date())
- assertEquals(authUser.id, data.updatedBy)
+ assertEquals(authUser.id, data.whoUpdated)
}
/*
// Test for checking if ArDocDetail is dirty when we fetch arDocDetails from arDoc
View
27 test/integration/nine/tests/ChildDomTests.groovy
@@ -0,0 +1,27 @@
+package nine.tests
+
+import org.junit.*
+
+
+class ChildDomTests extends BaseInt{
+
+ def grailsApplication
+
+ void testConstaints() {
+ def art = grailsApplication.getDomainClass("nine.tests.ChildDom")
+ assert art
+ assert art.constraints.childProp.getAppliedConstraint('nullable').isNullable() == false
+ assert art.constraints.childProp.getAppliedConstraint('blank').isBlank() == false
+ assert art.constraints.superProp.getAppliedConstraint('nullable').isNullable() == true
+ assert art.constraints.updatedBy.getAppliedConstraint('nullable').isNullable() == true
+ assert art.constraints.createdDate.getAppliedConstraint('nullable').isNullable() == false
+ art.constraints.each{k,v->
+ println "$k:$v"
+ }
+ }
+
+ void testConstaintInhertence() {
+ def cdom = new ChildDom(childProp:"test")
+ assert cdom.save()
+ }
+}
View
35 test/integration/nine/tests/IdGeneratorTests.groovy
@@ -1,35 +0,0 @@
-package nine.tests
-
-import grails.test.*
-import groovy.sql.Sql
-
-/**
- * Uses the doms domain to test the created by and edited by fields and CreateEditeStamp ASTrandformer
- *
-**/
-class IdGeneratorTests extends BaseInt {
- def sessionFactory
-
- void setUp() {
- super.setUp();
- }
-
-
- void testIdGen() {
- def dom = new TestDomain(name:"blah")
- if( !dom.save(flush:true) ) {
- dom.errors.each {
- println it
- }
- }
- assertNotNull(dom.id);
- def sql = new Sql(sessionFactory.getCurrentSession().connection());
- def sqlCall = "select nextId from NewObjectId where KeyName = 'TestDomains.OID'"
- println sqlCall
- def data = sql.firstRow(sqlCall)
- assertNotNull(data)
- println "nextId is ${data.nextId}"
- assert data.nextId > 10000
- }
-
-}
View
35 test/integration/nine/tests/ValDirtyTests.groovy
@@ -0,0 +1,35 @@
+package nine.tests
+
+import grails.test.*
+import groovy.sql.Sql
+import java.sql.ResultSet
+import org.apache.commons.lang.time.DateUtils
+
+/**
+ * Uses the doms domain to test the created by and edited by fields and CreateEditeStamp ASTrandformer
+ *
+**/
+class ValDirtyTests extends BaseInt {
+ def sessionFactory
+ def dataSource
+ def grailsApplication
+
+ void setUp() {
+ super.setUp();
+ }
+ void test_validate(){
+ def vd = new ValDirty(name:'gg')
+ //vd.name = "gg"
+ println "before save"
+ vd.save(failOnError:true)
+ println "after save"
+ }
+ void test_dirty(){
+ def vd = new ValDirty(name:'gg')
+ vd.save(failOnError:true)
+ def vd2 = ValDirty.findById(vd.id)
+ vd2.name = "hh"
+ assert vd2.isDirty()
+ }
+
+}
View
8 test/projects/joda/.gitignore
@@ -0,0 +1,8 @@
+.grails/
+grails-*.zip
+web-app/WEB-INF/classes/
+target/
+target-eclipse/
+c.tld
+fmt.tld
+stacktrace.log
View
7 test/projects/joda/application.properties
@@ -0,0 +1,7 @@
+#Grails Metadata file
+#Tue Jan 03 09:56:55 CST 2012
+app.grails.version=2.0.0
+app.name=joda
+app.servlet.version=2.5
+app.version=0.1
+
View
5 test/projects/joda/grails-app/conf/ApplicationResources.groovy
@@ -0,0 +1,5 @@
+modules = {
+ application {
+ resource url:'js/application.js'
+ }
+}
View
7 test/projects/joda/grails-app/conf/BootStrap.groovy
@@ -0,0 +1,7 @@
+class BootStrap {
+
+ def init = { servletContext ->
+ }
+ def destroy = {
+ }
+}
View
49 test/projects/joda/grails-app/conf/BuildConfig.groovy
@@ -0,0 +1,49 @@
+grails.servlet.version = "2.5" // Change depending on target container compliance (2.5 or 3.0)
+grails.project.class.dir = "target/classes"
+grails.project.test.class.dir = "target/test-classes"
+grails.project.test.reports.dir = "target/test-reports"
+grails.project.target.level = 1.6
+grails.project.source.level = 1.6
+
+//grails.project.war.file = "target/${appName}-${appVersion}.war"
+
+grails.project.dependency.resolution = {
+ // inherit Grails' default dependencies
+ inherits("global") {
+ // uncomment to disable ehcache
+ // excludes 'ehcache'
+ }
+ log "error" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
+ checksums true // Whether to verify checksums on resolve
+
+ repositories {
+ inherits true // Whether to inherit repository definitions from plugins
+ grailsPlugins()
+ grailsHome()
+ grailsCentral()
+ mavenCentral()
+
+ // uncomment these to enable remote dependency resolution from public Maven repositories
+ //mavenCentral()
+ //mavenLocal()
+ //mavenRepo "http://snapshots.repository.codehaus.org"
+ //mavenRepo "http://repository.codehaus.org"
+ //mavenRepo "http://download.java.net/maven/2/"
+ //mavenRepo "http://repository.jboss.com/maven2/"
+ }
+ dependencies {
+ // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
+ compile "org.jadira.usertype:usertype.jodatime:1.9"
+ // runtime 'mysql:mysql-connector-java:5.1.16'
+ }
+
+ plugins {
+ runtime ":hibernate:$grailsVersion"
+ build ":tomcat:$grailsVersion"
+ compile ":joda-time:1.3.1"
+
+ runtime ":jquery:1.7.1"
+ }
+}
+
+grails.plugin.location.'audit-trail' = "../../.."
View
108 test/projects/joda/grails-app/conf/Config.groovy
@@ -0,0 +1,108 @@
+// locations to search for config files that get merged into the main config
+// config files can either be Java properties files or ConfigSlurper scripts
+
+// grails.config.locations = [ "classpath:${appName}-config.properties",
+// "classpath:${appName}-config.groovy",
+// "file:${userHome}/.grails/${appName}-config.properties",
+// "file:${userHome}/.grails/${appName}-config.groovy"]
+
+// if (System.properties["${appName}.config.location"]) {
+// grails.config.locations << "file:" + System.properties["${appName}.config.location"]
+// }
+
+
+grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
+grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
+grails.mime.use.accept.header = false
+grails.mime.types = [ html: ['text/html','application/xhtml+xml'],
+ xml: ['text/xml', 'application/xml'],
+ text: 'text/plain',
+ js: 'text/javascript',
+ rss: 'application/rss+xml',
+ atom: 'application/atom+xml',
+ css: 'text/css',
+ csv: 'text/csv',
+ all: '*/*',
+ json: ['application/json','text/json'],
+ form: 'application/x-www-form-urlencoded',
+ multipartForm: 'multipart/form-data'
+ ]
+
+// URL Mapping Cache Max Size, defaults to 5000
+//grails.urlmapping.cache.maxsize = 1000
+
+// What URL patterns should be processed by the resources plugin
+grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
+
+
+// The default codec used to encode data with ${}
+grails.views.default.codec = "none" // none, html, base64
+grails.views.gsp.encoding = "UTF-8"
+grails.converters.encoding = "UTF-8"
+// enable Sitemesh preprocessing of GSP pages
+grails.views.gsp.sitemesh.preprocess = true
+// scaffolding templates configuration
+grails.scaffolding.templates.domainSuffix = 'Instance'
+
+// Set to false to use the new Grails 1.2 JSONBuilder in the render method
+grails.json.legacy.builder = false
+
+
+// request parameters to mask when logging exceptions
+grails.exceptionresolver.params.exclude = ['password']
+
+// enable query caching by default
+grails.hibernate.cache.queries = true
+
+// set per-environment serverURL stem for creating absolute links
+environments {
+ development {
+ grails.logging.jul.usebridge = true
+ }
+ production {
+ grails.logging.jul.usebridge = false
+ // TODO: grails.serverURL = "http://www.changeme.com"
+ }
+}
+
+// log4j configuration
+log4j = {
+ // Example of changing the log pattern for the default console
+ // appender:
+ //
+ //appenders {
+ // console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
+ //}
+
+ error 'org.codehaus.groovy.grails.web.servlet', // controllers
+ 'org.codehaus.groovy.grails.web.pages', // GSP
+ 'org.codehaus.groovy.grails.web.sitemesh', // layouts
+ 'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
+ 'org.codehaus.groovy.grails.web.mapping', // URL mapping
+ 'org.codehaus.groovy.grails.commons', // core / classloading
+ 'org.codehaus.groovy.grails.plugins', // plugins
+ 'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
+ 'org.springframework',
+ 'org.hibernate',
+ 'net.sf.ehcache.hibernate'
+}
+
+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 "RON"
+ }
+ }
+ }
+}
+
View
43 test/projects/joda/grails-app/conf/DataSource.groovy
@@ -0,0 +1,43 @@
+dataSource {
+ pooled = true
+ driverClassName = "org.h2.Driver"
+ username = "sa"
+ password = ""
+}
+hibernate {
+ cache.use_second_level_cache = true
+ cache.use_query_cache = true
+ cache.region.factory_class = 'net.sf.ehcache.hibernate.EhCacheRegionFactory'
+}
+// environment specific settings
+environments {
+ development {
+ dataSource {
+ dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', ''
+ url = "jdbc:h2:mem:devDb;MVCC=TRUE"
+ }
+ }
+ test {
+ dataSource {
+ dbCreate = "update"
+ url = "jdbc:h2:mem:testDb;MVCC=TRUE"
+ }
+ }
+ production {
+ dataSource {
+ dbCreate = "update"
+ url = "jdbc:h2:prodDb;MVCC=TRUE"
+ pooled = true
+ properties {
+ maxActive = -1
+ minEvictableIdleTimeMillis=1800000
+ timeBetweenEvictionRunsMillis=1800000
+ numTestsPerEvictionRun=3
+ testOnBorrow=true
+ testWhileIdle=true
+ testOnReturn=true
+ validationQuery="SELECT 1"
+ }
+ }
+ }
+}
View
13 test/projects/joda/grails-app/conf/UrlMappings.groovy
@@ -0,0 +1,13 @@
+class UrlMappings {
+
+ static mappings = {
+ "/$controller/$action?/$id?"{
+ constraints {
+ // apply constraints here
+ }
+ }
+
+ "/"(view:"/index")
+ "500"(view:'/error')
+ }
+}
View
3  test/projects/joda/grails-app/conf/spring/resources.groovy
@@ -0,0 +1,3 @@
+// Place your Spring DSL code here
+beans = {
+}
View
8 test/projects/joda/grails-app/domain/joda/AuditThis.groovy
@@ -0,0 +1,8 @@
+package joda
+
+@gorm.AuditStamp
+class AuditThis {
+
+ String name
+
+}
View
55 test/projects/joda/grails-app/i18n/messages.properties
@@ -0,0 +1,55 @@
+default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}]
+default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL
+default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number
+default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address
+default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}]
+default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}]
+default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}]
+default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}]
+default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}]
+default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}]
+default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation
+default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}]
+default.blank.message=Property [{0}] of class [{1}] cannot be blank
+default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}]
+default.null.message=Property [{0}] of class [{1}] cannot be null
+default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique
+
+default.paginate.prev=Previous
+default.paginate.next=Next
+default.boolean.true=True
+default.boolean.false=False
+default.date.format=yyyy-MM-dd HH:mm:ss z
+default.number.format=0
+
+default.created.message={0} {1} created
+default.updated.message={0} {1} updated
+default.deleted.message={0} {1} deleted
+default.not.deleted.message={0} {1} could not be deleted
+default.not.found.message={0} not found with id {1}
+default.optimistic.locking.failure=Another user has updated this {0} while you were editing
+
+default.home.label=Home
+default.list.label={0} List
+default.add.label=Add {0}
+default.new.label=New {0}
+default.create.label=Create {0}
+default.show.label=Show {0}
+default.edit.label=Edit {0}
+
+default.button.create.label=Create
+default.button.edit.label=Edit
+default.button.update.label=Update
+default.button.delete.label=Delete
+default.button.delete.confirm.message=Are you sure?
+
+# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author)
+typeMismatch.java.net.URL=Property {0} must be a valid URL
+typeMismatch.java.net.URI=Property {0} must be a valid URI
+typeMismatch.java.util.Date=Property {0} must be a valid Date
+typeMismatch.java.lang.Double=Property {0} must be a valid number
+typeMismatch.java.lang.Integer=Property {0} must be a valid number
+typeMismatch.java.lang.Long=Property {0} must be a valid number
+typeMismatch.java.lang.Short=Property {0} must be a valid number
+typeMismatch.java.math.BigDecimal=Property {0} must be a valid number
+typeMismatch.java.math.BigInteger=Property {0} must be a valid number
View
11 test/projects/joda/grails-app/views/error.gsp
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Grails Runtime Exception</title>
+ <meta name="layout" content="main">
+ <link rel="stylesheet" href="${resource(dir: 'css', file: 'errors.css')}" type="text/css">
+ </head>
+ <body>
+ <g:renderException exception="${exception}" />
+ </body>
+</html>
View
116 test/projects/joda/grails-app/views/index.gsp
@@ -0,0 +1,116 @@
+<!doctype html>
+<html>
+ <head>
+ <meta name="layout" content="main"/>
+ <title>Welcome to Grails</title>
+ <style type="text/css" media="screen">
+ #status {
+ background-color: #eee;
+ border: .2em solid #fff;
+ margin: 2em 2em 1em;
+ padding: 1em;
+ width: 12em;
+ float: left;
+ -moz-box-shadow: 0px 0px 1.25em #ccc;
+ -webkit-box-shadow: 0px 0px 1.25em #ccc;
+ box-shadow: 0px 0px 1.25em #ccc;
+ -moz-border-radius: 0.6em;
+ -webkit-border-radius: 0.6em;
+ border-radius: 0.6em;
+ }
+
+ .ie6 #status {
+ display: inline; /* float double margin fix http://www.positioniseverything.net/explorer/doubled-margin.html */
+ }
+
+ #status ul {
+ font-size: 0.9em;
+ list-style-type: none;
+ margin-bottom: 0.6em;
+ padding: 0;
+ }
+
+ #status h1 {
+ text-transform: uppercase;
+ font-size: 1.1em;
+ margin: 0 0 0.3em;
+ }
+
+ #page-body {
+ margin: 2em 1em 1.25em 18em;
+ }
+
+ h2 {
+ margin-top: 1em;
+ margin-bottom: 0.3em;
+ font-size: 1em;
+ }
+
+ p {
+ margin: 0.25em 0;
+ }
+
+ #controller-list ul {
+ list-style-position: inside;
+ }
+
+ #controller-list li {
+ list-style-position: inside;
+ margin: 0.25em 0;
+ }
+
+ @media screen and (max-width: 480px) {
+ #status {
+ display: none;
+ }
+
+ #page-body {
+ margin: 0 1em 1em;
+ }
+
+ #page-body h1 {
+ margin-top: 0;
+ }
+ }
+ </style>
+ </head>
+ <body>
+ <a href="#page-body" class="skip"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
+ <div id="status" role="complementary">
+ <h1>Application Status</h1>
+ <ul>
+ <li>App version: <g:meta name="app.version"/></li>
+ <li>Grails version: <g:meta name="app.grails.version"/></li>
+ <li>Groovy version: ${org.codehaus.groovy.runtime.InvokerHelper.getVersion()}</li>
+ <li>JVM version: ${System.getProperty('java.version')}</li>
+ <li>Reloading active: ${grails.util.Environment.reloadingAgentEnabled}</li>
+ <li>Controllers: ${grailsApplication.controllerClasses.size()}</li>
+ <li>Domains: ${grailsApplication.domainClasses.size()}</li>
+ <li>Services: ${grailsApplication.serviceClasses.size()}</li>
+ <li>Tag Libraries: ${grailsApplication.tagLibClasses.size()}</li>
+ </ul>
+ <h1>Installed Plugins</h1>
+ <ul>
+ <g:each var="plugin" in="${applicationContext.getBean('pluginManager').allPlugins}">
+ <li>${plugin.name} - ${plugin.version}</li>
+ </g:each>
+ </ul>
+ </div>
+ <div id="page-body" role="main">
+ <h1>Welcome to Grails</h1>
+ <p>Congratulations, you have successfully started your first Grails application! At the moment
+ this is the default page, feel free to modify it to either redirect to a controller or display whatever
+ content you may choose. Below is a list of controllers that are currently deployed in this application,
+ click on each to execute its default action:</p>
+
+ <div id="controller-list" role="navigation">
+ <h2>Available Controllers:</h2>
+ <ul>
+ <g:each var="c" in="${grailsApplication.controllerClasses.sort { it.fullName } }">
+ <li class="controller"><g:link controller="${c.logicalPropertyName}">${c.fullName}</g:link></li>
+ </g:each>
+ </ul>
+ </div>
+ </div>
+ </body>
+</html>
View
28 test/projects/joda/grails-app/views/layouts/main.gsp
@@ -0,0 +1,28 @@
+<!doctype html>
+<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
+<!--[if IE 7 ]> <html lang="en" class="no-js ie7"> <![endif]-->
+<!--[if IE 8 ]> <html lang="en" class="no-js ie8"> <![endif]-->
+<!--[if IE 9 ]> <html lang="en" class="no-js ie9"> <![endif]-->
+<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="no-js"><!--<![endif]-->
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <title><g:layoutTitle default="Grails"/></title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
+ <link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
+ <link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
+ <link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
+ <link rel="stylesheet" href="${resource(dir: 'css', file: 'mobile.css')}" type="text/css">
+ <g:layoutHead/>
+ <r:layoutResources />
+ </head>
+ <body>
+ <div id="grailsLogo" role="banner"><a href="http://grails.org"><img src="${resource(dir: 'images', file: 'grails_logo.png')}" alt="Grails"/></a></div>
+ <g:layoutBody/>
+ <div class="footer" role="contentinfo"></div>
+ <div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
+ <g:javascript library="application"/>
+ <r:layoutResources />
+ </body>
+</html>
View
31 test/projects/joda/test/integration/joda/AuditThisTests.groovy
@@ -0,0 +1,31 @@
+package joda
+
+import static org.junit.Assert.*
+import org.junit.*
+
+class AuditThisTests {
+
+ def grailsApplication
+
+ @Before
+ void setUp() {
+ // Setup logic here
+ }
+
+ @After
+ void tearDown() {
+ // Tear down logic here
+ }
+
+
+ @Test
+ void testSomething() {
+ def audit = new AuditThis(name:'billy boy')
+ assert audit.save(flush:true,failOnError:true)
+ assert audit.createdDate
+ assert audit.createdDate == audit.editedDate
+ assert audit.createdBy == "RON"
+ assert audit.editedBy == "RON"
+
+ }
+}
View
27 test/projects/joda/test/unit/joda/AuditUnitTests.groovy
@@ -0,0 +1,27 @@
+package joda
+
+import static org.junit.Assert.*
+
+import grails.test.mixin.*
+import grails.test.mixin.support.*
+import org.junit.*
+
+/**
+ * See the API for {@link grails.test.mixin.support.GrailsUnitTestMixin} for usage instructions
+ */
+@TestMixin(GrailsUnitTestMixin)
+class AuditUnitTests {
+
+ void setUp() {
+ // Setup logic here
+ }
+
+ void tearDown() {
+ // Tear down logic here
+ }
+
+ void testSomething() {
+ def audit = new AuditThis(name:'billy boy')
+ assert audit
+ }
+}
View
33 test/projects/joda/web-app/WEB-INF/applicationContext.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
+
+ <bean id="grailsApplication" class="org.codehaus.groovy.grails.commons.GrailsApplicationFactoryBean">
+ <description>Grails application factory bean</description>
+ <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+ <property name="grailsResourceLoader" ref="grailsResourceLoader" />
+ </bean>
+
+ <bean id="pluginManager" class="org.codehaus.groovy.grails.plugins.GrailsPluginManagerFactoryBean">
+ <description>A bean that manages Grails plugins</description>
+ <property name="grailsDescriptor" value="/WEB-INF/grails.xml" />
+ <property name="application" ref="grailsApplication" />
+ </bean>
+
+ <bean id="grailsConfigurator" class="org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator">
+ <constructor-arg>
+ <ref bean="grailsApplication" />
+ </constructor-arg>
+ <property name="pluginManager" ref="pluginManager" />
+ </bean>
+
+ <bean id="grailsResourceLoader" class="org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean" />
+
+ <bean id="characterEncodingFilter" class="org.springframework.web.filter.CharacterEncodingFilter">
+ <property name="encoding">
+ <value>utf-8</value>
+ </property>
+ </bean>
+</beans>
View
14 test/projects/joda/web-app/WEB-INF/sitemesh.xml
@@ -0,0 +1,14 @@
+<sitemesh>
+ <page-parsers>
+ <parser content-type="text/html"
+ class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+ <parser content-type="text/html;charset=ISO-8859-1"
+ class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+ <parser content-type="text/html;charset=UTF-8"
+ class="org.codehaus.groovy.grails.web.sitemesh.GrailsHTMLPageParser" />
+ </page-parsers>
+
+ <decorator-mappers>
+ <mapper class="org.codehaus.groovy.grails.web.sitemesh.GrailsLayoutDecoratorMapper" />
+ </decorator-mappers>
+</sitemesh>
View
550 test/projects/joda/web-app/WEB-INF/tld/grails.tld
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+ http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
+ version="2.0">
+ <description>The Grails custom tag library</description>
+ <tlib-version>0.2</tlib-version>
+ <short-name>grails</short-name>
+ <uri>http://grails.codehaus.org/tags</uri>
+
+ <tag>
+ <name>link</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspLinkTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <name>action</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>controller</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>id</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>url</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>params</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>form</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspFormTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <name>action</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>controller</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>id</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>url</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>method</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>select</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspSelectTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <name>name</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>value</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>optionKey</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>optionValue</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>datePicker</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspDatePickerTag</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <name>name</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>value</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>precision</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>false</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>currencySelect</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspCurrencySelectTag</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <name>name</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>value</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>localeSelect</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspLocaleSelectTag</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <name>name</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>value</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>timeZoneSelect</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspTimeZoneSelectTag</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <name>name</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>value</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>checkBox</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspCheckboxTag</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <name>name</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>value</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>hasErrors</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspHasErrorsTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <name>model</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>bean</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>field</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>false</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>eachError</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <name>model</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>bean</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>field</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>false</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>renderErrors</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <name>model</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>bean</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>field</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>as</name>
+ <required>true</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>false</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>message</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspMessageTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>
+ <name>code</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>error</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>default</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>false</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>remoteFunction</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteFunctionTag</tag-class>
+ <body-content>empty</body-content>
+ <attribute>
+ <name>before</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>after</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>action</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>controller</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>id</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>url</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>params</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>asynchronous</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>method</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>update</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>onSuccess</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>onFailure</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>onComplete</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>onLoading</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>onLoaded</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <attribute>
+ <name>onInteractive</name>
+ <required>false</required>
+ <rtexprvalue>true</rtexprvalue>
+ </attribute>
+ <dynamic-attributes>true</dynamic-attributes>
+ </tag>
+ <tag>
+ <name>remoteLink</name>
+ <tag-class>org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteLinkTag</tag-class>
+ <body-content>JSP</body-content>
+ <attribute>