diff --git a/build.gradle b/build.gradle index 2dfa3a1c..eecb7a6a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,11 @@ buildscript { repositories { mavenLocal() - mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } maven { url "https://repo.grails.org/grails/core" } } dependencies { - classpath "org.grails:grails-gradle-plugin:$grailsVersion" + classpath "org.grails:grails-gradle-plugin:$grailsGradlePluginVersion" classpath 'io.github.groovylang.groovydoc:groovydoc-gradle-plugin:1.0.1' classpath "io.github.gradle-nexus:publish-plugin:1.1.0" } @@ -33,26 +33,23 @@ repositories { } dependencies { - provided 'org.springframework.boot:spring-boot-starter-logging' - provided "org.springframework.boot:spring-boot-starter-actuator" - provided "org.springframework.boot:spring-boot-autoconfigure" - provided "org.springframework.boot:spring-boot-starter-tomcat" + compileOnly 'org.springframework.boot:spring-boot-starter-logging' + compileOnly "org.springframework.boot:spring-boot-starter-actuator" + compileOnly "org.springframework.boot:spring-boot-autoconfigure" + compileOnly "org.springframework.boot:spring-boot-starter-tomcat" - provided "org.grails:grails-web-boot" - provided "org.grails:grails-dependencies" - provided "javax.servlet:javax.servlet-api:$servletApiVersion" + compileOnly "org.grails:grails-web-boot" + compileOnly "org.grails:grails-dependencies" - compile "org.grails:scaffolding-core" + implementation "org.grails:scaffolding-core" - testCompile "org.grails:grails-web-testing-support" - testCompile "org.grails:grails-gorm-testing-support" + testImplementation "org.grails:grails-web-testing-support" + testImplementation "org.grails:grails-gorm-testing-support" - console "org.grails:grails-console" - - testCompile 'org.javassist:javassist:3.29.0-GA' - testCompile "org.codehaus.groovy:groovy-dateutil" - testCompile "cglib:cglib-nodep:2.2.2" - testCompile("org.jodd:jodd-wot:$joddWotVersion") { + testImplementation 'org.javassist:javassist:3.29.0-GA' + testImplementation "org.codehaus.groovy:groovy-dateutil" + testImplementation "cglib:cglib-nodep:2.2.2" + testImplementation("org.jodd:jodd-wot:$joddWotVersion") { exclude module: 'slf4j-api' exclude module: 'asm' } @@ -64,6 +61,10 @@ tasks.withType(GroovyCompile) { } } +tasks.withType(Test) { + useJUnitPlatform() +} + publishing { publications { maven(MavenPublication) { @@ -182,14 +183,14 @@ asciidoctor { outputDir = "${buildDir}/asciidoc" } -task apiDocs(type: Copy) { +tasks.register('apiDocs', Copy) { from groovydoc.outputs.files into file("${buildDir}/asciidoc/api") } asciidoctor.dependsOn(apiDocs) -task snapshotVersion { +tasks.register('snapshotVersion') { doLast { if (isReleaseVersion) { ant.propertyfile(file: "gradle.properties") { diff --git a/gradle.properties b/gradle.properties index da7f52d8..b9f7b09c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,8 @@ #Tue, 02 Jan 2024 08:12:44 +0000 version=4.0.1-SNAPSHOT -grailsVersion=4.1.3 -scaffoldingVersion=2.0.0.RC1 -cglibNodepVersion=3.2.9 +grailsVersion=5.3.5 +grailsGradlePluginVersion=5.3.1 joddWotVersion=3.3.8 -servletApiVersion=4.0.1 asciidoc=true githubSlug=gpc/fields githubBranch=master diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 457aad0d..e708b1c0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7a0e9a89..ffed3a25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Nov 27 07:46:21 CET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-all.zip diff --git a/gradlew b/gradlew index af6708ff..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,19 +156,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat old mode 100644 new mode 100755 index 0f8d5937..ac1b06f9 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/docs/asciidoc/changelog.adoc b/src/docs/asciidoc/changelog.adoc index e6e1d141..1f52f841 100644 --- a/src/docs/asciidoc/changelog.adoc +++ b/src/docs/asciidoc/changelog.adoc @@ -1,5 +1,17 @@ == Changelog +=== Version 5.0.0 - Grails 5.3.x + +In your `build.gradle` file, use: + +[source,groovy] +---- +implementation 'io.github.gpc:fields:5.0.0' +---- + +Changelog: +https://github.com/gpc/fields/compare/v4.0.0\...v5.0.0 + === Version 4.0.0 - Grails 4.1.x **Important** @@ -9,7 +21,7 @@ New `group` id. In your `build.gradle` file, use: -[source,groovy,subs="attributes"] +[source,groovy] ---- compile 'io.github.gpc:fields:4.0.0' ---- diff --git a/src/docs/asciidoc/installation.adoc b/src/docs/asciidoc/installation.adoc index 43b55ac1..b56d665c 100644 --- a/src/docs/asciidoc/installation.adoc +++ b/src/docs/asciidoc/installation.adoc @@ -4,6 +4,6 @@ The plugin is available on Maven Central and should be a dependency like this: [source,groovy,subs="attributes"] ---- dependencies { - compile 'io.github.gpc:fields:{version}' + implementation 'io.github.gpc:fields:{version}' } ---- diff --git a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessor.groovy b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessor.groovy index 8e5eedd0..fabe1e0a 100644 --- a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessor.groovy +++ b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessor.groovy @@ -17,104 +17,104 @@ package grails.plugin.formfields import grails.core.GrailsDomainClass -import org.grails.scaffolding.model.property.Constrained import org.grails.datastore.mapping.model.PersistentEntity import org.grails.datastore.mapping.model.PersistentProperty +import org.grails.scaffolding.model.property.Constrained import org.springframework.validation.FieldError interface BeanPropertyAccessor { - /** - * @return the object at the root of a path expression, e.g. for a `person` bean and `address.street` then `person` is returned. - */ - Object getRootBean() - - /** - * @return the type of the object at the root of a path expression, e.g. for a `person` bean and `address.street` then the type of `person` is returned. - */ - Class getRootBeanType() - - /** - * @return the full path from the root bean to the requested property. - */ - String getPathFromRoot() - - /** - * @return the name of the property at the end of the path, e.g. for `address.home.street`, `street` is returned. - */ - String getPropertyName() - - /** - * @return the type of the object that owns the property at the end of the path, e.g. for a `address.home.street` then the type of `home` is returned. - */ - Class getBeanType() - - /** - * @return the GORM domain type of `beanType`. This will be null if `beanType` is not a domain class. - * @deprecated use {@link #getEntity} - */ - @Deprecated - GrailsDomainClass getBeanClass() - - /** - * @return the GORM domain type of `beanType`. This will be null if `beanType` is not a domain class. - */ - PersistentEntity getEntity() - - /** - * @return all superclasses and interfaces of `beanType` excluding `Object`, `Serializable`, `Comparable` and `Cloneable`. - */ - List getBeanSuperclasses() - - /** - * @return the type of the property at the end of the path, e.g. for `address.home.street` then the type of `street` is returned. - */ - Class getPropertyType() - - /** - * @return all superclasses and interfaces of `propertyType` excluding `Object`, `Serializable`, `Comparable` and `Cloneable`. - */ - List getPropertyTypeSuperclasses() - - /** - * @return the value of the property at the end of the path, e.g. for `address.home.street` then the value of `street` is returned. - */ - Object getValue() - - /** - * @return the GORM persistent property descriptor for the property at the end of the path, e.g. for `address.home.street` then the descriptor of `street` is returned. This will be null for non-domain properties. - */ - PersistentProperty getDomainProperty() - - /** - * @return the constraints of the property at the end of the path, e.g. for `address.home.street` then the constraints of `street` are returned. This will be null for non-domain properties. - */ - Constrained getConstraints() - - /** - * @return the i18n keys used to resolve a label for the property at the end of the path in order of preference. - */ - List getLabelKeys() - - /** - * @return default label text for the property at the end of the path. - */ - String getDefaultLabel() - - /** - * @return the resolved messages for any validation errors present on the property at the end of the path. This will be an empty list if there are no errors or the property is not a validateable type. - */ - List getErrors() - - /** - * @return whether or not the property is required as determined by constraints. This will always be false for non-validateable types. - */ - boolean isRequired() - - /** - * @return whether or not the property has any validation errors (i.e. `getErrors` will return a non-empty list). This will always be false for non-validateable types. - */ - boolean isInvalid() + /** + * @return the object at the root of a path expression, e.g. for a `person` bean and `address.street` then `person` is returned. + */ + Object getRootBean() + + /** + * @return the type of the object at the root of a path expression, e.g. for a `person` bean and `address.street` then the type of `person` is returned. + */ + Class getRootBeanType() + + /** + * @return the full path from the root bean to the requested property. + */ + String getPathFromRoot() + + /** + * @return the name of the property at the end of the path, e.g. for `address.home.street`, `street` is returned. + */ + String getPropertyName() + + /** + * @return the type of the object that owns the property at the end of the path, e.g. for a `address.home.street` then the type of `home` is returned. + */ + Class getBeanType() + + /** + * @return the GORM domain type of `beanType`. This will be null if `beanType` is not a domain class. + * @deprecated use {@link #getEntity} + */ + @Deprecated + GrailsDomainClass getBeanClass() + + /** + * @return the GORM domain type of `beanType`. This will be null if `beanType` is not a domain class. + */ + PersistentEntity getEntity() + + /** + * @return all superclasses and interfaces of `beanType` excluding `Object`, `Serializable`, `Comparable` and `Cloneable`. + */ + List getBeanSuperclasses() + + /** + * @return the type of the property at the end of the path, e.g. for `address.home.street` then the type of `street` is returned. + */ + Class getPropertyType() + + /** + * @return all superclasses and interfaces of `propertyType` excluding `Object`, `Serializable`, `Comparable` and `Cloneable`. + */ + List getPropertyTypeSuperclasses() + + /** + * @return the value of the property at the end of the path, e.g. for `address.home.street` then the value of `street` is returned. + */ + Object getValue() + + /** + * @return the GORM persistent property descriptor for the property at the end of the path, e.g. for `address.home.street` then the descriptor of `street` is returned. This will be null for non-domain properties. + */ + PersistentProperty getDomainProperty() + + /** + * @return the constraints of the property at the end of the path, e.g. for `address.home.street` then the constraints of `street` are returned. This will be null for non-domain properties. + */ + Constrained getConstraints() + + /** + * @return the i18n keys used to resolve a label for the property at the end of the path in order of preference. + */ + List getLabelKeys() + + /** + * @return default label text for the property at the end of the path. + */ + String getDefaultLabel() + + /** + * @return the resolved messages for any validation errors present on the property at the end of the path. This will be an empty list if there are no errors or the property is not a validateable type. + */ + List getErrors() + + /** + * @return whether or not the property is required as determined by constraints. This will always be false for non-validateable types. + */ + boolean isRequired() + + /** + * @return whether or not the property has any validation errors (i.e. `getErrors` will return a non-empty list). This will always be false for non-validateable types. + */ + boolean isInvalid() } diff --git a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy index 128233d7..9e041caf 100644 --- a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy +++ b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorFactory.groovy @@ -17,11 +17,13 @@ package grails.plugin.formfields import grails.core.GrailsApplication +import grails.core.support.GrailsApplicationAware +import grails.core.support.proxy.ProxyHandler +import grails.gorm.validation.ConstrainedProperty import grails.gorm.validation.DefaultConstrainedProperty import grails.validation.Validateable +import groovy.transform.CompileStatic import groovy.transform.PackageScope -import grails.core.support.GrailsApplicationAware -import grails.core.support.proxy.ProxyHandler import org.grails.datastore.gorm.validation.constraints.eval.ConstraintsEvaluator import org.grails.datastore.gorm.validation.constraints.registry.DefaultConstraintRegistry import org.grails.datastore.mapping.model.MappingContext @@ -38,158 +40,159 @@ import org.springframework.beans.PropertyAccessorFactory import org.springframework.context.support.StaticMessageSource import java.lang.reflect.ParameterizedType +import java.lang.reflect.Type +import java.util.regex.Matcher import java.util.regex.Pattern +@CompileStatic class BeanPropertyAccessorFactory implements GrailsApplicationAware { - GrailsApplication grailsApplication - ConstraintsEvaluator constraintsEvaluator - ProxyHandler proxyHandler - DomainPropertyFactory fieldsDomainPropertyFactory - MappingContext grailsDomainClassMappingContext - - BeanPropertyAccessor accessorFor(bean, String propertyPath) { - if (bean == null) { - new PropertyPathAccessor(propertyPath) - } else { - resolvePropertyFromPath(bean, propertyPath) - } - } - - private PersistentEntity resolveDomainClass(Class beanClass) { - grailsDomainClassMappingContext.getPersistentEntity(beanClass.name) - } + GrailsApplication grailsApplication + ConstraintsEvaluator constraintsEvaluator + ProxyHandler proxyHandler + DomainPropertyFactory fieldsDomainPropertyFactory + MappingContext grailsDomainClassMappingContext + + BeanPropertyAccessor accessorFor(bean, String propertyPath) { + if (bean == null) { + new PropertyPathAccessor(propertyPath) + } else { + resolvePropertyFromPath(bean, propertyPath) + } + } - private BeanPropertyAccessor resolvePropertyFromPath(bean, String pathFromRoot) { - def beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean) - def pathElements = pathFromRoot.tokenize(".") + private PersistentEntity resolveDomainClass(Class beanClass) { + grailsDomainClassMappingContext.getPersistentEntity(beanClass.name) + } - def params = [rootBean: bean, rootBeanType: bean.getClass(), pathFromRoot: pathFromRoot, grailsApplication: grailsApplication] + private BeanPropertyAccessor resolvePropertyFromPath(bean, String pathFromRoot) { + BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean) + List pathElements = pathFromRoot.tokenize(".") - DomainProperty domainProperty = resolvePropertyFromPathComponents(beanWrapper, pathElements, params) + Map params = [rootBean: bean, rootBeanType: bean.getClass(), pathFromRoot: pathFromRoot, grailsApplication: grailsApplication] - if (domainProperty != null) { - new DelegatingBeanPropertyAccessorImpl(bean, params.value, params.propertyType, pathFromRoot, domainProperty) - } else { - new BeanPropertyAccessorImpl(params) - } + DomainProperty domainProperty = resolvePropertyFromPathComponents(beanWrapper, pathElements, params) - } + if (domainProperty != null) { + new DelegatingBeanPropertyAccessorImpl(bean, params.value, params.propertyType as Class, pathFromRoot, domainProperty) + } else { + new BeanPropertyAccessorImpl(params) + } - private DomainProperty resolvePropertyFromPathComponents(BeanWrapper beanWrapper, List pathElements, params) { - def propertyName = pathElements.remove(0) - PersistentEntity beanClass = resolveDomainClass(beanWrapper.wrappedClass) - def propertyType = resolvePropertyType(beanWrapper, beanClass, propertyName) - def value = beanWrapper.getPropertyValue(propertyName) - if (pathElements.empty) { - params.value = value - params.propertyType = propertyType - - PersistentProperty persistentProperty - String nameWithoutIndex = stripIndex(propertyName) - if (beanClass != null) { - persistentProperty = beanClass.getPropertyByName(nameWithoutIndex) - if (!persistentProperty && beanClass.isIdentityName(nameWithoutIndex)) { - persistentProperty = beanClass.identity - } - } + } - if (persistentProperty != null) { - fieldsDomainPropertyFactory.build(persistentProperty) - } else { - params.entity = beanClass - params.beanType = beanWrapper.wrappedClass - params.propertyType = propertyType - params.propertyName = nameWithoutIndex - params.domainProperty = null - params.constraints = resolveConstraints(beanWrapper, params.propertyName) - null - } - } else { - resolvePropertyFromPathComponents(beanWrapperFor(propertyType, value), pathElements, params) - } - } + private DomainProperty resolvePropertyFromPathComponents(BeanWrapper beanWrapper, List pathElements, Map params) { + String propertyName = pathElements.remove(0) + PersistentEntity beanClass = resolveDomainClass(beanWrapper.wrappedClass) + Class propertyType = resolvePropertyType(beanWrapper, beanClass, propertyName) + Object value = beanWrapper.getPropertyValue(propertyName) + if (pathElements.empty) { + params.value = value + params.propertyType = propertyType + + PersistentProperty persistentProperty + String nameWithoutIndex = stripIndex(propertyName) + if (beanClass != null) { + persistentProperty = beanClass.getPropertyByName(nameWithoutIndex) + if (!persistentProperty && beanClass.isIdentityName(nameWithoutIndex)) { + persistentProperty = beanClass.identity + } + } + + if (persistentProperty != null) { + return fieldsDomainPropertyFactory.build(persistentProperty) + } else { + params.entity = beanClass + params.beanType = beanWrapper.wrappedClass + params.propertyType = propertyType + params.propertyName = nameWithoutIndex + params.domainProperty = null + params.constraints = resolveConstraints(beanWrapper, params.propertyName as String) + return null + } + } else { + return resolvePropertyFromPathComponents(beanWrapperFor(propertyType, value), pathElements, params) + } + } - private Constrained resolveConstraints(BeanWrapper beanWrapper, String propertyName) { + private Constrained resolveConstraints(BeanWrapper beanWrapper, String propertyName) { Class type = beanWrapper.wrappedClass boolean defaultNullable = Validateable.class.isAssignableFrom(type) ? type.metaClass.invokeStaticMethod(type, 'defaultNullable') : false - grails.gorm.validation.Constrained constraint = constraintsEvaluator.evaluate(type, defaultNullable)[propertyName] - if (!constraint) { - constraint = createDefaultConstraint(beanWrapper, propertyName) - } - new Constrained(constraint) - } + ConstrainedProperty constraint = constraintsEvaluator.evaluate(type, defaultNullable)[propertyName] - private grails.gorm.validation.Constrained createDefaultConstraint(BeanWrapper beanWrapper, String propertyName) { - def defaultConstraint = new DefaultConstrainedProperty(beanWrapper.wrappedClass, propertyName, beanWrapper.getPropertyType(propertyName), new DefaultConstraintRegistry(new StaticMessageSource())) - defaultConstraint.nullable = true - defaultConstraint + new Constrained(constraint ?: createDefaultConstraint(beanWrapper, propertyName)) } - private Class resolvePropertyType(BeanWrapper beanWrapper, PersistentEntity beanClass, String propertyName) { - Class propertyType = null - if (beanClass) { - propertyType = resolveDomainPropertyType(beanClass, propertyName) - } - if (!propertyType) { - propertyType = resolveNonDomainPropertyType(beanWrapper, propertyName) - } - propertyType - } + private static ConstrainedProperty createDefaultConstraint(BeanWrapper beanWrapper, String propertyName) { + new DefaultConstrainedProperty(beanWrapper.wrappedClass, propertyName, beanWrapper.getPropertyType(propertyName), new DefaultConstraintRegistry(new StaticMessageSource())).tap { + nullable = true + } + } - private Class resolveDomainPropertyType(PersistentEntity beanClass, String propertyName) { - def propertyNameWithoutIndex = stripIndex(propertyName) - def persistentProperty = beanClass.getPropertyByName(propertyNameWithoutIndex) - if (!persistentProperty && beanClass.isIdentityName(propertyNameWithoutIndex)) { - persistentProperty = beanClass.identity - } - if (!persistentProperty) { - return null - } - boolean isIndexed = propertyName =~ INDEXED_PROPERTY_PATTERN - if (isIndexed) { - if (persistentProperty instanceof Basic) { - persistentProperty.componentType - } else if (persistentProperty instanceof Association) { - persistentProperty.associatedEntity.javaClass - } - } else { - persistentProperty.type - } - } + private static Class resolvePropertyType(BeanWrapper beanWrapper, PersistentEntity beanClass, String propertyName) { + return resolveDomainPropertyType(beanClass, propertyName) ?: resolveNonDomainPropertyType(beanWrapper, propertyName) + } + + private static Class resolveDomainPropertyType(PersistentEntity beanClass, String propertyName) { + if(beanClass) { + String propertyNameWithoutIndex = stripIndex(propertyName) + PersistentProperty persistentProperty = beanClass.getPropertyByName(propertyNameWithoutIndex) + if (!persistentProperty && beanClass.isIdentityName(propertyNameWithoutIndex)) { + persistentProperty = beanClass.identity + } + if (!persistentProperty) { + return null + } + boolean isIndexed = propertyName =~ INDEXED_PROPERTY_PATTERN + if (isIndexed) { + if (persistentProperty instanceof Basic) { + return (persistentProperty as Basic).componentType + } else if (persistentProperty instanceof Association) { + return (persistentProperty as Association).associatedEntity.javaClass + } + } else { + return persistentProperty.type + } + } + return null + } - private Class resolveNonDomainPropertyType(BeanWrapper beanWrapper, String propertyName) { - def type = beanWrapper.getPropertyType(propertyName) + private static Class resolveNonDomainPropertyType(BeanWrapper beanWrapper, String propertyName) { + Class type = beanWrapper.getPropertyType(propertyName) if (type == null) { - def match = propertyName =~ INDEXED_PROPERTY_PATTERN + String match = getPropertyMatch(propertyName) if (match) { - def genericType = beanWrapper.getPropertyDescriptor(match[0][1]).readMethod.genericReturnType + Type genericType = beanWrapper.getPropertyDescriptor(match).readMethod.genericReturnType if (genericType instanceof ParameterizedType) { - switch (genericType.rawType) { + ParameterizedType parameterizedType = genericType as ParameterizedType + switch (parameterizedType.rawType) { case Collection: - type = genericType.actualTypeArguments[0] - break + return parameterizedType.actualTypeArguments[0] as Class case Map: - type = genericType.actualTypeArguments[1] - break + return parameterizedType.actualTypeArguments[1] as Class } } else { - type = Object + return Object } } } - type + return type } - private BeanWrapper beanWrapperFor(Class type, value) { - value ? PropertyAccessorFactory.forBeanPropertyAccess(proxyHandler.unwrapIfProxy(value)) : new BeanWrapperImpl(type) - } + private BeanWrapper beanWrapperFor(Class type, value) { + value ? PropertyAccessorFactory.forBeanPropertyAccess(proxyHandler.unwrapIfProxy(value)) : new BeanWrapperImpl(type) + } - private static final Pattern INDEXED_PROPERTY_PATTERN = ~/^(\w+)\[(.+)\]$/ + private static final Pattern INDEXED_PROPERTY_PATTERN = ~/^(\w+)\[(.+)]$/ - @PackageScope - static String stripIndex(String propertyName) { - def matcher = propertyName =~ INDEXED_PROPERTY_PATTERN - matcher.matches() ? matcher[0][1] : propertyName - } + private static String getPropertyMatch(String propertyName) { + Matcher matcher = propertyName =~ INDEXED_PROPERTY_PATTERN + matcher.matches() ? (matcher[0] as String[])[1] : null + } + + @PackageScope + static String stripIndex(String propertyName) { + def matcher = propertyName =~ INDEXED_PROPERTY_PATTERN + matcher.matches() ? (matcher[0] as String[])[1] : propertyName + } } diff --git a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy index 00b77975..64938b16 100644 --- a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy +++ b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy @@ -20,20 +20,23 @@ import grails.core.GrailsApplication import grails.core.GrailsDomainClass import grails.gorm.Entity import grails.gorm.validation.ConstrainedProperty -import grails.plugins.VersionComparator import grails.util.GrailsNameUtils +import grails.validation.Validateable import grails.web.databinding.WebDataBinding import groovy.transform.Canonical import groovy.transform.CompileStatic +import groovy.transform.Memoized import groovy.transform.TupleConstructor import org.apache.commons.lang.ClassUtils import org.grails.datastore.gorm.GormEntity +import org.grails.datastore.gorm.GormValidateable import org.grails.datastore.mapping.dirty.checking.DirtyCheckable import org.grails.datastore.mapping.model.PersistentEntity import org.grails.datastore.mapping.model.PersistentProperty import org.grails.scaffolding.model.property.Constrained import org.springframework.validation.FieldError +@CompileStatic @Canonical @TupleConstructor(includes = ['beanType', 'propertyName', 'propertyType']) class BeanPropertyAccessorImpl implements BeanPropertyAccessor { @@ -51,22 +54,10 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor { PersistentEntity entity GrailsApplication grailsApplication - /** - * Since Grails 2.3 blank values that are provided for String properties are - * converted to null by default - */ - @Lazy - private boolean convertBlanksToNull = { -> - - String applicationGrailsVersion = grailsApplication.metadata.getGrailsVersion() - boolean isAtLeastGrails2Point3 = new VersionComparator().compare(applicationGrailsVersion, '2.3') != -1 - - if (isAtLeastGrails2Point3) { - getDataBindingConfigParamValue('convertEmptyStringsToNull') && getDataBindingConfigParamValue('trimStrings') - } else { - false - } - }() + @Memoized + private boolean convertBlanksToNull() { + getDataBindingConfigParamValue('convertEmptyStringsToNull') && getDataBindingConfigParamValue('trimStrings') + } /** * Returns the effective value of a a boolean config param from the grails.databinding node @@ -90,7 +81,7 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor { [ "${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''), "${GrailsNameUtils.getPropertyName(beanType.simpleName)}.${propertyName}.label" - ].unique() + ].unique() as List } String getDefaultLabel() { @@ -98,12 +89,12 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor { } List getErrors() { - if (rootBean.metaClass.hasProperty(rootBean, 'errors') && rootBean.errors) { - - rootBean.errors.getFieldErrors(pathFromRoot) - } else { - [] + if (rootBean instanceof Validateable) { + return (rootBean as Validateable).errors.getFieldErrors(pathFromRoot) + } else if (rootBean instanceof GormValidateable) { + return (rootBean as GormValidateable).errors.getFieldErrors(pathFromRoot) } + return [] } boolean isRequired() { @@ -113,7 +104,7 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor { // if the property prohibits nulls and blanks are converted to nulls, then blanks will be prohibited even if a blank // constraint does not exist boolean hasBlankConstraint = constraints?.hasAppliedConstraint(ConstrainedProperty.BLANK_CONSTRAINT) - boolean blanksImplicitlyProhibited = !hasBlankConstraint && !constraints?.nullable && convertBlanksToNull + boolean blanksImplicitlyProhibited = !hasBlankConstraint && !constraints?.nullable && convertBlanksToNull() !constraints?.nullable && (!constraints?.blank || blanksImplicitlyProhibited) } else { !constraints?.nullable @@ -124,8 +115,7 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor { !errors.isEmpty() } - @CompileStatic - private List getSuperclassesAndInterfaces(Class type) { + private static List getSuperclassesAndInterfaces(Class type) { List superclasses = [] superclasses.addAll(ClassUtils.getAllSuperclasses(ClassUtils.primitiveToWrapper(type))) for (Object it in ClassUtils.getAllInterfaces(type)) { diff --git a/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy b/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy index f8209944..5b18994f 100644 --- a/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy +++ b/src/main/groovy/grails/plugin/formfields/DelegatingBeanPropertyAccessorImpl.groovy @@ -7,6 +7,7 @@ import grails.validation.Validateable import grails.web.databinding.WebDataBinding import groovy.transform.Canonical import groovy.transform.CompileStatic +import groovy.transform.ToString import org.apache.commons.lang.ClassUtils import org.grails.datastore.gorm.GormEntity import org.grails.datastore.gorm.GormValidateable @@ -19,141 +20,137 @@ import org.springframework.validation.Errors import org.springframework.validation.FieldError @CompileStatic -@Canonical(includes = ['beanType', 'propertyName', 'propertyType']) +@Canonical +@ToString(includes = ['beanType', 'propertyName', 'propertyType']) class DelegatingBeanPropertyAccessorImpl implements BeanPropertyAccessor { - private DomainProperty domainProperty - private Object rootBean - private Object value - private String pathFromRoot - final Class beanType - final String propertyName - final Class propertyType - - DelegatingBeanPropertyAccessorImpl(Object rootBean, Object value, Class propertyType, String pathFromRoot, DomainProperty domainProperty) { - this.rootBean = rootBean - this.value = value - this.pathFromRoot = pathFromRoot - this.domainProperty = domainProperty - this.propertyType = propertyType - this.propertyName = domainProperty.name - this.beanType = domainProperty.beanType - } - - @Override - Object getRootBean() { - rootBean - } - - @Override - Class getRootBeanType() { - rootBean.getClass() - } - - @Override - String getPathFromRoot() { - pathFromRoot - } - - @Override - @Deprecated - GrailsDomainClass getBeanClass() { - throw new UnsupportedOperationException() - } - - @Override - PersistentEntity getEntity() { - domainProperty.domainClass - } - - @Override - List getBeanSuperclasses() { - getSuperclassesAndInterfaces(beanType) - } - - @Override - List getPropertyTypeSuperclasses() { - getSuperclassesAndInterfaces(propertyType) - } - - @Override - Object getValue() { - value - } - - @Override - PersistentProperty getDomainProperty() { - domainProperty.persistentProperty - } - - @Override - Constrained getConstraints() { - domainProperty.constrained - } - - @Override - List getLabelKeys() { - List labelKeys = [] - if (rootBean) { - labelKeys.add("${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, '')) - } - labelKeys.addAll(domainProperty.labelKeys) - labelKeys.unique() - } - - @Override - String getDefaultLabel() { - domainProperty.defaultLabel - } - - @Override - List getErrors() { - Errors errors - if (rootBean instanceof Validateable) { - errors = ((Validateable) rootBean).errors - } else if (rootBean instanceof GormValidateable) { - errors = ((GormValidateable) rootBean).errors - } - if (errors) { - errors.getFieldErrors(pathFromRoot) - } else { - [] - } - } - - @Override - boolean isRequired() { - domainProperty.required - } - - @Override - boolean isInvalid() { - !errors.isEmpty() - } - - @Override - int hashCode() { - return Objects.hash(beanType, propertyName, propertyType) - } - - @Override - boolean equals(Object obj) { - this.hashCode() == obj?.hashCode() - } - - private List getSuperclassesAndInterfaces(Class type) { - List superclasses = [] - superclasses.addAll(ClassUtils.getAllSuperclasses(ClassUtils.primitiveToWrapper(type))) - for (Object it in ClassUtils.getAllInterfaces(type)) { - Class interfaceCls = (Class) it - String name = interfaceCls.name - if (name.indexOf('$') == -1) { - if (interfaceCls.package != GormEntity.package) { - superclasses.add(interfaceCls) - } - } - } - superclasses.removeAll([Object, GroovyObject, Serializable, Cloneable, Comparable, WebDataBinding, DirtyCheckable, Entity]) - return superclasses.unique() - } + private DomainProperty domainProperty + private Object rootBean + private Object value + private String pathFromRoot + final Class beanType + final String propertyName + final Class propertyType + + DelegatingBeanPropertyAccessorImpl(Object rootBean, Object value, Class propertyType, String pathFromRoot, DomainProperty domainProperty) { + this.rootBean = rootBean + this.value = value + this.pathFromRoot = pathFromRoot + this.domainProperty = domainProperty + this.propertyType = propertyType + this.propertyName = domainProperty.name + this.beanType = domainProperty.beanType + } + + @Override + Object getRootBean() { + rootBean + } + + @Override + Class getRootBeanType() { + rootBean.getClass() + } + + @Override + String getPathFromRoot() { + pathFromRoot + } + + @Override + @Deprecated + GrailsDomainClass getBeanClass() { + throw new UnsupportedOperationException() + } + + @Override + PersistentEntity getEntity() { + domainProperty.domainClass + } + + @Override + List getBeanSuperclasses() { + getSuperclassesAndInterfaces(beanType) + } + + @Override + List getPropertyTypeSuperclasses() { + getSuperclassesAndInterfaces(propertyType) + } + + @Override + Object getValue() { + value + } + + @Override + PersistentProperty getDomainProperty() { + domainProperty.persistentProperty + } + + @Override + Constrained getConstraints() { + domainProperty.constrained + } + + @Override + List getLabelKeys() { + List labelKeys = [] + if (rootBean) { + labelKeys.add("${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, '')) + } + labelKeys.addAll(domainProperty.labelKeys) + labelKeys.unique() as List + } + + @Override + String getDefaultLabel() { + domainProperty.defaultLabel + } + + @Override + List getErrors() { + if (rootBean instanceof Validateable) { + return (rootBean as Validateable).errors.getFieldErrors(pathFromRoot) + } else if (rootBean instanceof GormValidateable) { + return (rootBean as GormValidateable).errors.getFieldErrors(pathFromRoot) + } + return [] + } + + @Override + boolean isRequired() { + domainProperty.required + } + + @Override + boolean isInvalid() { + !errors.isEmpty() + } + + @Override + int hashCode() { + return Objects.hash(beanType, propertyName, propertyType) + } + + @Override + boolean equals(Object obj) { + this.hashCode() == obj?.hashCode() + } + + private List getSuperclassesAndInterfaces(Class type) { + List superclasses = [] + superclasses.addAll(ClassUtils.getAllSuperclasses(ClassUtils.primitiveToWrapper(type))) + for (Object it in ClassUtils.getAllInterfaces(type)) { + Class interfaceCls = (Class) it + String name = interfaceCls.name + if (name.indexOf('$') == -1) { + if (interfaceCls.package != GormEntity.package) { + superclasses.add(interfaceCls) + } + } + } + superclasses.removeAll([Object, GroovyObject, Serializable, Cloneable, Comparable, WebDataBinding, DirtyCheckable, Entity]) + return superclasses.unique() + } } diff --git a/src/main/groovy/grails/plugin/formfields/FieldsGrailsPlugin.groovy b/src/main/groovy/grails/plugin/formfields/FieldsGrailsPlugin.groovy index 6fc84a85..b8245e19 100644 --- a/src/main/groovy/grails/plugin/formfields/FieldsGrailsPlugin.groovy +++ b/src/main/groovy/grails/plugin/formfields/FieldsGrailsPlugin.groovy @@ -16,30 +16,32 @@ package grails.plugin.formfields import grails.plugins.Plugin - import org.grails.scaffolding.model.DomainModelServiceImpl import org.grails.scaffolding.model.property.DomainPropertyFactoryImpl class FieldsGrailsPlugin extends Plugin { - static final String CONSTRAINTS_EVALULATOR_BEAN_NAME = 'validateableConstraintsEvaluator' + static final String CONSTRAINTS_EVALULATOR_BEAN_NAME = 'validateableConstraintsEvaluator' - def grailsVersion = '3.0 > *' + def grailsVersion = '5.0 > *' - def loadAfter = ['domainClass'] + def loadAfter = ['domainClass'] - @Override - Closure doWithSpring() {{-> - beanPropertyAccessorFactory(BeanPropertyAccessorFactory) { - constraintsEvaluator = ref(CONSTRAINTS_EVALULATOR_BEAN_NAME) - proxyHandler = ref('proxyHandler') - fieldsDomainPropertyFactory = ref('fieldsDomainPropertyFactory') - grailsDomainClassMappingContext = ref('grailsDomainClassMappingContext') - } - formFieldsTemplateService(FormFieldsTemplateService) - fieldsDomainPropertyFactory(DomainPropertyFactoryImpl) - domainModelService(DomainModelServiceImpl) { - domainPropertyFactory: ref(fieldsDomainPropertyFactory) - } - }} + @Override + Closure doWithSpring() { + { -> + beanPropertyAccessorFactory(BeanPropertyAccessorFactory) { + constraintsEvaluator = ref(CONSTRAINTS_EVALULATOR_BEAN_NAME) + proxyHandler = ref('proxyHandler') + fieldsDomainPropertyFactory = ref('fieldsDomainPropertyFactory') + grailsDomainClassMappingContext = ref('grailsDomainClassMappingContext') + } + formFieldsTemplateService(FormFieldsTemplateService) + fieldsDomainPropertyFactory(DomainPropertyFactoryImpl) + domainModelService(DomainModelServiceImpl) { + domainPropertyFactory: + ref(fieldsDomainPropertyFactory) + } + } + } } diff --git a/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy b/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy index 79737242..2c14aeae 100644 --- a/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy +++ b/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy @@ -17,8 +17,10 @@ package grails.plugin.formfields import grails.core.GrailsApplication +import grails.plugins.GrailsPlugin import grails.plugins.GrailsPluginManager import grails.util.GrailsNameUtils +import groovy.transform.CompileStatic import groovy.transform.Memoized import groovy.util.logging.Slf4j import org.grails.datastore.mapping.model.types.ManyToMany @@ -28,14 +30,13 @@ import org.grails.datastore.mapping.model.types.OneToOne import org.grails.scaffolding.model.property.Constrained import org.grails.web.gsp.io.GrailsConventionGroovyPageLocator import org.grails.web.servlet.mvc.GrailsWebRequest -import org.grails.web.util.GrailsApplicationAttributes import org.springframework.beans.factory.annotation.Autowired -import org.springframework.web.context.request.RequestAttributes import org.springframework.web.context.request.RequestContextHolder import static org.grails.io.support.GrailsResourceUtils.appendPiecesForUri @Slf4j +@CompileStatic class FormFieldsTemplateService { public static final String SETTING_WIDGET_PREFIX = 'grails.plugin.fields.widgetPrefix' @@ -55,69 +56,16 @@ class FormFieldsTemplateService { return shouldCache ? widgetPrefixCached : widgetPrefixNotCached } - @Memoized - private String getWidgetPrefixCached() { - widgetPrefixNotCached - } - - private String getWidgetPrefixNotCached() { - return grailsApplication?.config?.getProperty(SETTING_WIDGET_PREFIX, 'widget-') - } - - String getTemplateFor(String property) { shouldCache ? getTemplateForCached(property) : getTemplateForNotCached(property) } - - @Memoized - private getTemplateForCached(String templateProperty) { - getTemplateForNotCached(templateProperty) - } - Map findTemplate(BeanPropertyAccessor propertyAccessor, String templateName, String templatesFolder, String theme = null) { shouldCache ? findTemplateCached(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, theme) : findTemplateNotCached(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, theme) } - private getTemplateForNotCached(String templateProperty) { - return grailsApplication?.config?.getProperty("grails.plugin.fields.$templateProperty", templateProperty) ?: templateProperty - } - - @Memoized - private findTemplateCached(BeanPropertyAccessor propertyAccessor, String controllerNamespace, String controllerName, String actionName, String templateName, String templatesFolder, String themeName) { - findTemplateNotCached(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, themeName) - } - - private findTemplateNotCached(BeanPropertyAccessor propertyAccessor, String controllerNamespace, String controllerName, String actionName, String templateName, String templatesFolder, String themeName) { - List candidatePaths - if (themeName) { - //if theme is specified, first resolve all theme paths and then all the default paths - String themeFolder = THEMES_FOLDER + "/" + themeName - candidatePaths = candidateTemplatePaths(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, themeFolder) - candidatePaths = candidatePaths + candidateTemplatePaths(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, null) - } else { - candidatePaths = candidateTemplatePaths(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, null) - } - - candidatePaths.findResult { String path -> - log.debug "looking for template with path $path" - def source = groovyPageLocator.findTemplateByPath(path) - if (source) { - Map template = [path: path] - def plugin = pluginManager.allPlugins.find { - source.URI.startsWith(it.pluginPath) - } - template.plugin = plugin?.name - log.debug "found template $template.path ${plugin ? "in $template.plugin plugin" : ''}" - return template - } else { - null - } - } - } - static String toPropertyNameFormat(Class type) { def propertyNameFormat = GrailsNameUtils.getLogicalPropertyName(type.canonicalName, '') if (propertyNameFormat.endsWith('[]')) { @@ -194,15 +142,6 @@ class FormFieldsTemplateService { templateResolveOrder } - private String getAssociationPath(BeanPropertyAccessor propertyAccessor) { - String associationPath = null - if (propertyAccessor.domainProperty instanceof OneToOne) associationPath = 'oneToOne' - if (propertyAccessor.domainProperty instanceof OneToMany) associationPath = 'oneToMany' - if (propertyAccessor.domainProperty instanceof ManyToMany) associationPath = 'manyToMany' - if (propertyAccessor.domainProperty instanceof ManyToOne) associationPath = 'manyToOne' - associationPath - } - protected String getWidget(Constrained cp, Class propertyType) { if (null == cp) { return null @@ -224,18 +163,54 @@ class FormFieldsTemplateService { return widget } - private String getControllerNamespace() { - if (GrailsWebRequest.metaClass.respondsTo(GrailsWebRequest, "getControllerNamespace").size() > 0) { - return RequestContextHolder.requestAttributes?.getAttribute(GrailsApplicationAttributes.CONTROLLER_NAMESPACE_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST) - } + @Memoized + private String getWidgetPrefixCached() { + widgetPrefixNotCached + } + + private String getWidgetPrefixNotCached() { + return grailsApplication?.config?.getProperty(SETTING_WIDGET_PREFIX, 'widget-') + } + + @Memoized + private String getTemplateForCached(String templateProperty) { + getTemplateForNotCached(templateProperty) + } + + private String getTemplateForNotCached(String templateProperty) { + grailsApplication?.config?.getProperty("grails.plugin.fields.$templateProperty", templateProperty) ?: templateProperty } - private String getControllerName() { - RequestContextHolder.requestAttributes?.controllerName + @Memoized + private Map findTemplateCached(BeanPropertyAccessor propertyAccessor, String controllerNamespace, String controllerName, String actionName, String templateName, String templatesFolder, String themeName) { + findTemplateNotCached(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, themeName) } - private String getActionName() { - RequestContextHolder.requestAttributes?.actionName + private Map findTemplateNotCached(BeanPropertyAccessor propertyAccessor, String controllerNamespace, String controllerName, String actionName, String templateName, String templatesFolder, String themeName) { + List candidatePaths + if (themeName) { + //if theme is specified, first resolve all theme paths and then all the default paths + String themeFolder = THEMES_FOLDER + "/" + themeName + candidatePaths = candidateTemplatePaths(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, themeFolder) + candidatePaths = candidatePaths + candidateTemplatePaths(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, null) + } else { + candidatePaths = candidateTemplatePaths(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, null) + } + + candidatePaths.findResult { String path -> + log.debug "looking for template with path $path" + def source = groovyPageLocator.findTemplateByPath(path) + if (source) { + Map template = [path: path] + GrailsPlugin plugin = pluginManager.allPlugins.find { + source.URI.startsWith(it.pluginPath) + } + template.plugin = plugin?.name + log.debug "found template $template.path ${plugin ? "in $template.plugin plugin" : ''}" + return template + } + return null + } } private boolean getShouldCache() { @@ -244,4 +219,30 @@ class FormFieldsTemplateService { return !cacheDisabled } + private static String getAssociationPath(BeanPropertyAccessor propertyAccessor) { + switch (propertyAccessor.domainProperty) { + case OneToOne: return 'oneToOne' + case OneToMany: return 'oneToMany' + case ManyToMany: return 'manyToMany' + case ManyToOne: return 'manyToOne' + default: return null + } + } + + private static String getControllerNamespace() { + return grailsWebRequest?.getControllerNamespace() + } + + private static String getControllerName() { + grailsWebRequest?.controllerName + } + + private static String getActionName() { + grailsWebRequest?.actionName + } + + private static GrailsWebRequest getGrailsWebRequest() { + RequestContextHolder.requestAttributes as GrailsWebRequest + } + } diff --git a/src/main/groovy/grails/plugin/formfields/PropertyPathAccessor.groovy b/src/main/groovy/grails/plugin/formfields/PropertyPathAccessor.groovy index da8b20bc..4071e479 100644 --- a/src/main/groovy/grails/plugin/formfields/PropertyPathAccessor.groovy +++ b/src/main/groovy/grails/plugin/formfields/PropertyPathAccessor.groovy @@ -1,51 +1,65 @@ package grails.plugin.formfields +import grails.core.GrailsDomainClass import grails.gorm.validation.DefaultConstrainedProperty import grails.util.GrailsNameUtils import groovy.transform.Canonical import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode import groovy.transform.ToString import org.grails.datastore.gorm.validation.constraints.registry.DefaultConstraintRegistry -import org.grails.datastore.gorm.validation.constraints.registry.DefaultValidatorRegistry import org.grails.datastore.mapping.model.PersistentEntity import org.grails.datastore.mapping.model.PersistentProperty import org.grails.scaffolding.model.property.Constrained import org.springframework.context.support.StaticMessageSource import org.springframework.validation.FieldError + import static grails.plugin.formfields.BeanPropertyAccessorFactory.stripIndex import static java.util.Collections.EMPTY_LIST import static org.apache.commons.lang.StringUtils.substringAfterLast -import grails.core.* @CompileStatic -@Canonical(includes = ['beanType', 'propertyName', 'propertyType']) +@Canonical +@ToString(includes = ['beanType', 'propertyName', 'propertyType']) class PropertyPathAccessor implements BeanPropertyAccessor { - final String pathFromRoot - final String propertyName = stripIndex pathFromRoot.contains('.') ? substringAfterLast(pathFromRoot, '.') : pathFromRoot - final Class beanType = null - final Class propertyType = Object - - PropertyPathAccessor(String pathFromRoot) { - this.pathFromRoot = pathFromRoot - } - - String getDefaultLabel() { - GrailsNameUtils.getNaturalName(propertyName) - } - - Object getRootBean() { null } - Class getRootBeanType() { null } - GrailsDomainClass getBeanClass() { null } - PersistentEntity getEntity() { null } - List getBeanSuperclasses() { EMPTY_LIST } - List getPropertyTypeSuperclasses() { EMPTY_LIST } - Object getValue() { null } - Constrained getConstraints() { new Constrained(new DefaultConstrainedProperty(Object, propertyName, String, new DefaultConstraintRegistry(new StaticMessageSource()))) } - PersistentProperty getDomainProperty() { null } - List getLabelKeys() { EMPTY_LIST } - List getErrors() { EMPTY_LIST } - boolean isRequired() { false } - boolean isInvalid() { false } + final String pathFromRoot + final String propertyName = stripIndex pathFromRoot.contains('.') ? substringAfterLast(pathFromRoot, '.') : pathFromRoot + final Class beanType = null + final Class propertyType = Object + + PropertyPathAccessor(String pathFromRoot) { + this.pathFromRoot = pathFromRoot + } + + String getDefaultLabel() { + GrailsNameUtils.getNaturalName(propertyName) + } + + Object getRootBean() { null } + + Class getRootBeanType() { null } + + GrailsDomainClass getBeanClass() { null } + + PersistentEntity getEntity() { null } + + List getBeanSuperclasses() { EMPTY_LIST } + + List getPropertyTypeSuperclasses() { EMPTY_LIST } + + Object getValue() { null } + + Constrained getConstraints() { + new Constrained(new DefaultConstrainedProperty(Object, propertyName, String, new DefaultConstraintRegistry(new StaticMessageSource()))) + } + + PersistentProperty getDomainProperty() { null } + + List getLabelKeys() { EMPTY_LIST } + + List getErrors() { EMPTY_LIST } + + boolean isRequired() { false } + + boolean isInvalid() { false } } diff --git a/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy b/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy index 21e54a5d..acf89670 100644 --- a/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy +++ b/src/test/groovy/grails/plugin/formfields/DomainClassPropertyAccessorSpec.groovy @@ -256,10 +256,10 @@ class DomainClassPropertyAccessorSpec extends BuildsAccessorFactory { person.address.country = "Australia" person.errors.rejectValue('address.country', 'not.inList') // http://jira.grails.org/browse/GRAILS-8480 - and: - def propertyAccessor = factory.accessorFor(person, "address.country") + when: + BeanPropertyAccessor propertyAccessor = factory.accessorFor(person, "address.country") - expect: + then: propertyAccessor.errors.first().code == "not.inList" propertyAccessor.invalid }