diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 00000000..ca1ffe0a --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,47 @@ +name: Java CI +on: + push: + branches: + - master + - upgrade/grails4 + pull_request: + branches: + - master + - upgrade/grails4 + workflow_dispatch: + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build: + permissions: + contents: read # to fetch code (actions/checkout) + checks: write # to publish result as PR check (scacap/action-surefire-report) + + runs-on: ubuntu-latest + strategy: + matrix: + java: ['8'] + env: + WORKSPACE: ${{ github.workspace }} + GRADLE_OPTS: -Xmx1500m -Dfile.encoding=UTF-8 + steps: + - uses: actions/checkout@v3 + - name: Set up JDK + uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: ${{ matrix.java }} + - name: Run Tests + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + id: tests + uses: gradle/gradle-build-action@v2 + with: + arguments: check + - name: Run Build + if: github.event_name == 'push' + id: build + uses: gradle/gradle-build-action@v2 + with: + arguments: build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 542397e2..61c541d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,8 @@ jobs: release: runs-on: ubuntu-latest env: - GIT_USER_NAME: sbglasius - GIT_USER_EMAIL: soeren@glasius.dk + GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }} + GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} steps: - uses: actions/checkout@v4 with: @@ -23,6 +23,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Publish to Sonatype OSSRH id: publish + if: steps.assemble.outcome == 'success' uses: gradle/gradle-build-action@v2 env: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} @@ -54,6 +55,28 @@ jobs: message: 'Set project version to next SNAPSHOT' - name: Export Gradle Properties uses: micronaut-projects/github-actions/export-gradle-properties@master + - name: Build documentation + id: asciidoctor + uses: gradle/gradle-build-action@v2 + env: + RELEASE_VERSION: ${{ steps.get_version.outputs.version-without-v }} + with: + arguments: -Pversion="${RELEASE_VERSION}" + - name: Export Gradle Properties + uses: micronaut-projects/github-actions/export-gradle-properties@master + - name: Publish to Github Pages + if: success() + uses: micronaut-projects/github-pages-deploy-action@master + env: + BETA: ${{ steps.get_version.outputs.isPrerelase }} + TARGET_REPOSITORY: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + BRANCH: gh-pages + FOLDER: build/asciidoc + DOC_FOLDER: latest + COMMIT_EMAIL: ${{ secrets.GIT_USER_EMAIL }} + COMMIT_NAME: ${{ secrets.GIT_USER_NAME }} + VERSION: ${{ steps.get_version.outputs.version-without-v }} - name: Run post-release if: success() uses: micronaut-projects/github-actions/post-release@master diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 363f1711..00000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: groovy -jdk: -- openjdk8 -- openjdk11 -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ -script: "./travis-build.sh" -after_failure: -- "./travis/publish_reports.sh" -env: - global: - - GIT_NAME="Graeme Rocher" - - GIT_EMAIL="graeme.rocher@gmail.com" - - secure: yjrvYcL2KStheccxqoc5qfL2lIplkzGmja7oc5ZyCELelYH6TxP2iCDSE2UvICymlrByAHk5rcoEqhH9vjkRYOQfgXWahT18RkppjFsgc3F0lD1ulhPhunYDaCi0xyyPMddVy8Y9wWbkAyl6MREGKVtKFpkFpJqM0uY+ieUuU1c= - - secure: g03YHB4AwAMBuTz3L0LCh3pf51Wnnm/noL26ZmHaSvuDbGcZakwIfgclo/7cs16obQXZkbFyj9PqCSeXSHttLBca0cRW+1MU8qTIRhz7zHOzoOEFyxjNRW2Oi9zqpYULFoKNNY95c0JosPnRLdcRbVwmKm6XtqNc6zCGMJ+x79A= - - secure: qwQDbAsXS01PdjtxFOyAn0y7epwLiowLKAvoyoU1Yutl/7zPZJxtqOVpEVjBc2qVCMKXBTR60tPtqH+mGjamB5GE5T4OYfxiPnpmXagp7eWCF/1swYrUKEUFDolEIM6aU6l6pui3wvmheHB0Mnk2i4DF0dNbWHps+NwhYgHXAfg= - - secure: Fk7KukPKRQu9WI0NSvczhLAbgvabCI8d+cV8jbKhahScQMhSObTaxqk/Tb1JaI1vRQ3IHksGbsIGOMx2jK5hntpjF8MCY1e8Y4rVkPNWxDqbtKvzCbWKNOhR5lfalCjn5Plx/qdh0TbNbiYswY/idmAVkVOfP7Wy2CKoadsP9HE= - - secure: DsItL1MTuV9GLC2JKblnFqTvj8swuM4C96sPHRjrz0OArs6QZ3oyuaYdWFQIQdgeaO08gvoyjjP5TrT5UWEO8j6ElAbQMoYISffcqSzmzo82rBQTrjNjWHQ98BOcM8ge1A8lInHEuOBnQVxpk4KB2SOPD2QJeUd1MC9qXZpWQig= - - secure: 3dy+H77W9DUZnie/8VkkknD3v93+r5HWR6qxKim4C81EOk/FPFp64ejc9TIjoIs+24SKCfSCDRe3O9EqyJRH6UGFPFBsap59CTQALppM7nI9ws0JzW/JmLIuqRhT+INgoVnD3ZWZ9onHFgwHZEWmhcRYY/iMybKrxXgI0rFXy18= - - secure: l7P4ZS1P2zl6fh1jQjOv28xl6uJUwxbcH7gorNly8kF5f14ebqH93uiGNctDHGHJ2a73iy632h8MG7btTdmP/s0t+Oz6ZZFyEUh4q6DVzOuOzJ6HEGn0u/uHdfgHACETwKqt8sQwmmYv4CV/EG9zgF8eIZGJMwrSCqtdHJTR5tI= -install: true -deploy: - provider: releases - api_key: - secure: xRdXMWiKZt6IbMQC2PLjq+EYhNqHuxEMrBKrK7TG0h/C3gdAZP3oH/s//jVr6BqKtbmbPduQTWX5DAy1K2XlvtdEEh0t19JCQSTRMRBRw2LQKoq6sQz8iKD0kspfE4zbywxSLvitbuppj4SPcl55hEQ6PBF0MtWWvVlVZ0dygbk= - file: build/libs/fields-${TRAVIS_TAG:1}.jar - on: - repo: grails-fields-plugin/grails-fields - tags: true diff --git a/build.gradle b/build.gradle index 7e6cc077..a8a057b7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,77 +1,178 @@ buildscript { - repositories { - mavenLocal() - maven { url "https://repo.grails.org/grails/core" } - } - dependencies { - classpath "org.grails:grails-gradle-plugin:$grailsVersion" - classpath 'io.github.groovylang.groovydoc:groovydoc-gradle-plugin:1.0.1' - } + repositories { + mavenLocal() + mavenCentral() + maven { url "https://repo.grails.org/grails/core" } + } + dependencies { + classpath "org.grails:grails-gradle-plugin:$grailsVersion" + classpath 'io.github.groovylang.groovydoc:groovydoc-gradle-plugin:1.0.1' + classpath "io.github.gradle-nexus:publish-plugin:1.1.0" + } +} +plugins { + id 'org.asciidoctor.jvm.convert' version '3.2.0' } version project.projectVersion -group "org.grails.plugins" - -apply plugin:"eclipse" -apply plugin:"idea" -apply plugin: "org.grails.grails-plugin" -apply plugin: "org.grails.grails-gsp" -apply plugin: "org.grails.grails-plugin-publish" -ext { - commonBuild = 'https://raw.githubusercontent.com/grails/grails-common-build/1549696f8d1a6bce3d84db9c4c1bfde61eddf175' +group 'org.grails.plugins' -} +apply plugin: 'org.grails.grails-plugin' +apply plugin: 'org.grails.grails-gsp' +apply plugin: "org.grails.grails-doc" +apply plugin: 'maven-publish' +apply plugin: 'signing' +apply plugin: "io.github.gradle-nexus.publish-plugin" sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { - mavenLocal() - mavenCentral() - maven { url "https://repo.grails.org/grails/core" } + mavenLocal() + mavenCentral() + maven { url "https://repo.grails.org/grails/core" } } 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" + 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" - provided "org.grails:grails-web-boot" + provided "org.grails:grails-web-boot" provided "org.grails:grails-dependencies" provided "javax.servlet:javax.servlet-api:$servletApiVersion" - compile "org.grails:scaffolding-core:$scaffoldingVersion" - - testCompile "org.grails:grails-web-testing-support:2.0.0.RC1" - testCompile "org.grails:grails-gorm-testing-support:2.0.0.RC1" + compile "org.grails:scaffolding-core" + + testCompile "org.grails:grails-web-testing-support" + testCompile "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") { + exclude module: 'slf4j-api' + exclude module: 'asm' + } +} + +tasks.withType(GroovyCompile) { + configure(groovyOptions) { + forkOptions.jvmArgs = ['-Xmx1024m'] + } +} - console "org.grails:grails-console" +publishing { + publications { + maven(MavenPublication) { + groupId = project.group + artifactId = 'jms' + version = project.version - testCompile 'javassist:javassist:3.12.0.GA' - testCompile "org.codehaus.groovy:groovy-dateutil:$groovyVersion" - testCompile "cglib:cglib-nodep:$cglibNodepVersion" - testCompile("org.jodd:jodd-wot:$joddWotVersion") { - exclude module: 'slf4j-api' - exclude module: 'asm' - } + from components.java + artifact sourcesJar + artifact javadocJar + + pom { + name = 'Jms' + description = 'JMS integration for Grails' + url = 'https://github.com/gpc/jms' + licenses { + license { + name = 'The Apache License, Version 2.0' + url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + developers { + developer { + id = 'brownj' + name = 'Jeff Scott Brown' + email = 'brownj@objectcomputing.com' + } + developer { + id = 'sbglasius' + name = 'Søren Berg Glasius' + email = 'soeren+gpc@glasius.dk' + } + } + scm { + connection = 'scm:git:git://github.com/gpc/jms.git' + developerConnection = 'scm:git:https://github.com/gpc/jms.git' + url = 'https://github.com/gpc/jms' + } + } + } + } } -grailsPublish { - userOrg = "grails" - githubSlug = project.githubSlug - license { - name = 'Apache-2.0' - } - title = project.title - desc = project.projectDesc - - developers = [ - graemerocher: "Graeme Rocher", - robfletcher: "Rob Fletcher", - sbglasius: "Søren Berg Glasius" - ] +ext."signing.keyId" = project.findProperty('signing.keyId') ?: System.getenv('SIGNING_KEY_ID') +ext."signing.password" = project.findProperty('signing.password') ?: System.getenv('SIGNING_PASSPHRASE') +ext."signing.secretKeyRingFile" = project.findProperty('signing.secretKeyRingFile') ?: (System.getenv('SIGNING_PASSPHRASE') ?: "${System.getProperty('user.home')}/.gnupg/secring.gpg") + +ext.isReleaseVersion = !version.endsWith("SNAPSHOT") + +afterEvaluate { + signing { + required { isReleaseVersion } + sign publishing.publications.maven + } } -apply from: "${commonBuild}/common-docs.gradle" +tasks.withType(Sign) { + onlyIf { isReleaseVersion } +} + +nexusPublishing { + repositories { + sonatype { + def ossUser = System.getenv("SONATYPE_USERNAME") ?: project.findProperty('sonatypeOss2Username') ?: '' + def ossPass = System.getenv("SONATYPE_PASSWORD") ?: project.findProperty("sonatypeOss2Password") ?: '' + def ossStagingProfileId = System.getenv("SONATYPE_STAGING_PROFILE_ID") ?: project.findProperty("sonatypeOssStagingProfileIdJms") ?: '' + + nexusUrl = uri("https://s01.oss.sonatype.org/service/local/") + snapshotRepositoryUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") + username = ossUser + password = ossPass + stagingProfileId = ossStagingProfileId + } + } +} + +asciidoctor { + resources { + from('src/docs/images') + into "./images" + } + sources { + include 'index.adoc' + } + attributes 'experimental': 'true', + 'compat-mode': 'true', + 'toc': 'left', + 'icons': 'font', + 'version': project.version, + 'sourcedir': 'src/main/groovy' + baseDirFollowsSourceDir() + outputDir = "${buildDir}/asciidoc" +} + +task apiDocs(type: Copy) { + from groovydoc.outputs.files + into file("${buildDir}/asciidoc/api") +} + +asciidoctor.dependsOn(apiDocs) + +task snapshotVersion { + doLast { + if (!project.version.endsWith('-SNAPSHOT')) { + ant.propertyfile(file: "gradle.properties") { + entry(key: "version", value: "${project.version}-SNAPSHOT") + } + } + } +} diff --git a/gradle.properties b/gradle.properties index f8ac7512..96d8e019 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,7 @@ projectVersion=3.0.0.BUILD-SNAPSHOT #projectVersion=2.2.7 -grailsVersion=4.0.0.M1 +grailsVersion=4.1.2 scaffoldingVersion=2.0.0.RC1 -groovyVersion=2.5.6 cglibNodepVersion=3.2.9 joddWotVersion=3.3.8 servletApiVersion=4.0.1 diff --git a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy index 20f8e260..22c9cea4 100644 --- a/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy +++ b/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy @@ -418,7 +418,10 @@ class FormFieldsTagLib { } private List resolvePropertyNames(PersistentEntity domainClass, Map attrs) { - if (attrs.containsKey('properties')) { + if (attrs.containsKey('order')) { + return getList(attrs.order) + } + else if (attrs.containsKey('properties')) { return getList(attrs.remove('properties')) } else { diff --git a/src/main/docs/guide/customizingFieldRendering.adoc b/src/docs/asciidoc/customizingFieldRendering.adoc similarity index 100% rename from src/main/docs/guide/customizingFieldRendering.adoc rename to src/docs/asciidoc/customizingFieldRendering.adoc diff --git a/src/main/docs/guide/embeddedProperties.adoc b/src/docs/asciidoc/embeddedProperties.adoc similarity index 100% rename from src/main/docs/guide/embeddedProperties.adoc rename to src/docs/asciidoc/embeddedProperties.adoc diff --git a/src/main/docs/guide/includingTemplatesInPlugins.adoc b/src/docs/asciidoc/includingTemplatesInPlugins.adoc similarity index 100% rename from src/main/docs/guide/includingTemplatesInPlugins.adoc rename to src/docs/asciidoc/includingTemplatesInPlugins.adoc diff --git a/src/main/docs/guide/index.adoc b/src/docs/asciidoc/index.adoc similarity index 96% rename from src/main/docs/guide/index.adoc rename to src/docs/asciidoc/index.adoc index 64f2c8a0..ede7cfc8 100644 --- a/src/main/docs/guide/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -1,5 +1,5 @@ -= My Title -:version: 2.1.4-SNAPSHOT += Fields Plugin +:version: 3.0.0-SNAPSHOT :source-highlighter: coderay :imagesdir: ./images diff --git a/src/main/docs/guide/introduction.adoc b/src/docs/asciidoc/introduction.adoc similarity index 100% rename from src/main/docs/guide/introduction.adoc rename to src/docs/asciidoc/introduction.adoc diff --git a/src/main/docs/guide/links.yml b/src/docs/asciidoc/links.yml similarity index 100% rename from src/main/docs/guide/links.yml rename to src/docs/asciidoc/links.yml diff --git a/src/main/docs/guide/performance.adoc b/src/docs/asciidoc/performance.adoc similarity index 100% rename from src/main/docs/guide/performance.adoc rename to src/docs/asciidoc/performance.adoc diff --git a/src/main/docs/ref/Tags/all.adoc b/src/docs/asciidoc/ref/Tags/all.adoc similarity index 100% rename from src/main/docs/ref/Tags/all.adoc rename to src/docs/asciidoc/ref/Tags/all.adoc diff --git a/src/main/docs/ref/Tags/display.adoc b/src/docs/asciidoc/ref/Tags/display.adoc similarity index 100% rename from src/main/docs/ref/Tags/display.adoc rename to src/docs/asciidoc/ref/Tags/display.adoc diff --git a/src/main/docs/ref/Tags/displayWidget.adoc b/src/docs/asciidoc/ref/Tags/displayWidget.adoc similarity index 100% rename from src/main/docs/ref/Tags/displayWidget.adoc rename to src/docs/asciidoc/ref/Tags/displayWidget.adoc diff --git a/src/main/docs/ref/Tags/field.adoc b/src/docs/asciidoc/ref/Tags/field.adoc similarity index 100% rename from src/main/docs/ref/Tags/field.adoc rename to src/docs/asciidoc/ref/Tags/field.adoc diff --git a/src/main/docs/ref/Tags/input.adoc b/src/docs/asciidoc/ref/Tags/input.adoc similarity index 100% rename from src/main/docs/ref/Tags/input.adoc rename to src/docs/asciidoc/ref/Tags/input.adoc diff --git a/src/main/docs/ref/Tags/table.adoc b/src/docs/asciidoc/ref/Tags/table.adoc similarity index 100% rename from src/main/docs/ref/Tags/table.adoc rename to src/docs/asciidoc/ref/Tags/table.adoc diff --git a/src/main/docs/ref/Tags/widget.adoc b/src/docs/asciidoc/ref/Tags/widget.adoc similarity index 100% rename from src/main/docs/ref/Tags/widget.adoc rename to src/docs/asciidoc/ref/Tags/widget.adoc diff --git a/src/main/docs/ref/Tags/with.adoc b/src/docs/asciidoc/ref/Tags/with.adoc similarity index 100% rename from src/main/docs/ref/Tags/with.adoc rename to src/docs/asciidoc/ref/Tags/with.adoc diff --git a/src/main/docs/resources/style/layout.html b/src/docs/asciidoc/resources/style/layout.html similarity index 100% rename from src/main/docs/resources/style/layout.html rename to src/docs/asciidoc/resources/style/layout.html diff --git a/src/main/docs/guide/scaffolding.adoc b/src/docs/asciidoc/scaffolding.adoc similarity index 100% rename from src/main/docs/guide/scaffolding.adoc rename to src/docs/asciidoc/scaffolding.adoc diff --git a/src/main/docs/guide/themes.adoc b/src/docs/asciidoc/themes.adoc similarity index 100% rename from src/main/docs/guide/themes.adoc rename to src/docs/asciidoc/themes.adoc diff --git a/src/main/docs/guide/toc.yml b/src/docs/asciidoc/toc.yml similarity index 100% rename from src/main/docs/guide/toc.yml rename to src/docs/asciidoc/toc.yml diff --git a/src/main/docs/guide/usage.adoc b/src/docs/asciidoc/usage.adoc similarity index 100% rename from src/main/docs/guide/usage.adoc rename to src/docs/asciidoc/usage.adoc diff --git a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy index ad59a42d..00b77975 100644 --- a/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy +++ b/src/main/groovy/grails/plugin/formfields/BeanPropertyAccessorImpl.groovy @@ -16,15 +16,17 @@ package grails.plugin.formfields +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.web.databinding.WebDataBinding import groovy.transform.Canonical import groovy.transform.CompileStatic +import groovy.transform.TupleConstructor import org.apache.commons.lang.ClassUtils -import grails.core.* -import grails.plugins.VersionComparator import org.grails.datastore.gorm.GormEntity import org.grails.datastore.mapping.dirty.checking.DirtyCheckable import org.grails.datastore.mapping.model.PersistentEntity @@ -32,22 +34,22 @@ import org.grails.datastore.mapping.model.PersistentProperty import org.grails.scaffolding.model.property.Constrained import org.springframework.validation.FieldError - -@Canonical(includes = ['beanType', 'propertyName', 'propertyType']) +@Canonical +@TupleConstructor(includes = ['beanType', 'propertyName', 'propertyType']) class BeanPropertyAccessorImpl implements BeanPropertyAccessor { - Object rootBean - Class rootBeanType - GrailsDomainClass beanClass - Class beanType - String pathFromRoot - String propertyName - Class propertyType - Constrained constraints - Object value - PersistentProperty domainProperty - PersistentEntity entity - GrailsApplication grailsApplication + Object rootBean + Class rootBeanType + GrailsDomainClass beanClass + Class beanType + String pathFromRoot + String propertyName + Class propertyType + Constrained constraints + Object value + PersistentProperty domainProperty + PersistentEntity entity + GrailsApplication grailsApplication /** * Since Grails 2.3 blank values that are provided for String properties are @@ -76,66 +78,66 @@ class BeanPropertyAccessorImpl implements BeanPropertyAccessor { grailsApplication.config.getProperty("grails.databinding.$paramName", Boolean, defaultParamValue) } - List getBeanSuperclasses() { - getSuperclassesAndInterfaces(beanType) - } - - List getPropertyTypeSuperclasses() { - getSuperclassesAndInterfaces(propertyType) - } - - List getLabelKeys() { - [ - "${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''), - "${GrailsNameUtils.getPropertyName(beanType.simpleName)}.${propertyName}.label" - ].unique() - } - - String getDefaultLabel() { - GrailsNameUtils.getNaturalName(propertyName) - } - - List getErrors() { - if (rootBean.metaClass.hasProperty(rootBean, 'errors') && rootBean.errors) { - - rootBean.errors.getFieldErrors(pathFromRoot) - } else { - [] - } - } - - boolean isRequired() { - if (propertyType in [Boolean, boolean]) { - false - } else if (propertyType == String) { + List getBeanSuperclasses() { + getSuperclassesAndInterfaces(beanType) + } + + List getPropertyTypeSuperclasses() { + getSuperclassesAndInterfaces(propertyType) + } + + List getLabelKeys() { + [ + "${GrailsNameUtils.getPropertyName(rootBeanType.simpleName)}.${pathFromRoot}.label".replaceAll(/\[(.+)\]/, ''), + "${GrailsNameUtils.getPropertyName(beanType.simpleName)}.${propertyName}.label" + ].unique() + } + + String getDefaultLabel() { + GrailsNameUtils.getNaturalName(propertyName) + } + + List getErrors() { + if (rootBean.metaClass.hasProperty(rootBean, 'errors') && rootBean.errors) { + + rootBean.errors.getFieldErrors(pathFromRoot) + } else { + [] + } + } + + boolean isRequired() { + if (propertyType in [Boolean, boolean]) { + false + } else if (propertyType == String) { // 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 blanksImplicityProhibited = !hasBlankConstraint && !constraints?.nullable && convertBlanksToNull - !constraints?.nullable && (!constraints?.blank || blanksImplicityProhibited) - } else { - !constraints?.nullable - } - } - - boolean isInvalid() { - !errors.isEmpty() - } - - @CompileStatic - 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() - } + boolean blanksImplicitlyProhibited = !hasBlankConstraint && !constraints?.nullable && convertBlanksToNull + !constraints?.nullable && (!constraints?.blank || blanksImplicitlyProhibited) + } else { + !constraints?.nullable + } + } + + boolean isInvalid() { + !errors.isEmpty() + } + + @CompileStatic + 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/FormFieldsTemplateService.groovy b/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy index 50055d75..79737242 100644 --- a/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy +++ b/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy @@ -19,6 +19,7 @@ package grails.plugin.formfields import grails.core.GrailsApplication import grails.plugins.GrailsPluginManager import grails.util.GrailsNameUtils +import groovy.transform.Memoized import groovy.util.logging.Slf4j import org.grails.datastore.mapping.model.types.ManyToMany import org.grails.datastore.mapping.model.types.ManyToOne @@ -36,7 +37,9 @@ import static org.grails.io.support.GrailsResourceUtils.appendPiecesForUri @Slf4j class FormFieldsTemplateService { + public static final String SETTING_WIDGET_PREFIX = 'grails.plugin.fields.widgetPrefix' + public static final String DISABLE_LOOKUP_CACHE = 'grails.plugin.fields.disableLookupCache' private static final String THEMES_FOLDER = "_themes" @Autowired @@ -48,51 +51,57 @@ class FormFieldsTemplateService { @Autowired GrailsPluginManager pluginManager - String getWidgetPrefix(){ - Closure widgetPrefixNameResolver = getWidgetPrefixName - return widgetPrefixNameResolver() + String getWidgetPrefix() { + return shouldCache ? widgetPrefixCached : widgetPrefixNotCached } - @Lazy - private Closure getWidgetPrefixName = shouldCache() ? getWidgetPrefixNameCacheable.memoize() : getWidgetPrefixNameCacheable + @Memoized + private String getWidgetPrefixCached() { + widgetPrefixNotCached + } - private Closure getWidgetPrefixNameCacheable = { -> + private String getWidgetPrefixNotCached() { return grailsApplication?.config?.getProperty(SETTING_WIDGET_PREFIX, 'widget-') } - Map findTemplate(BeanPropertyAccessor propertyAccessor, String templateName, String templatesFolder, String theme = null) { - // it looks like the assignment below is redundant, but tests fail if findTemplateCached is invoked directly - Closure templateFinder = findTemplateCached - templateFinder(propertyAccessor, controllerNamespace, controllerName, actionName, templateName, templatesFolder, theme) + + String getTemplateFor(String property) { + shouldCache ? getTemplateForCached(property) : getTemplateForNotCached(property) } - String getTemplateFor(String property){ - Closure nameFinder = getTemplateName - nameFinder(property) + + @Memoized + private getTemplateForCached(String templateProperty) { + getTemplateForNotCached(templateProperty) } - @Lazy - private Closure getTemplateName = shouldCache() ? getTemplateNameCacheable.memoize() : getTemplateNameCacheable + 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 getTemplateNameCacheable = { String templateProperty -> + private getTemplateForNotCached(String templateProperty) { return grailsApplication?.config?.getProperty("grails.plugin.fields.$templateProperty", templateProperty) ?: templateProperty } - @Lazy - private Closure findTemplateCached = shouldCache() ? findTemplateCacheable.memoize() : findTemplateCacheable + @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 findTemplateCacheable = {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) - } + 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 -> + candidatePaths.findResult { String path -> log.debug "looking for template with path $path" def source = groovyPageLocator.findTemplateByPath(path) if (source) { @@ -229,9 +238,9 @@ class FormFieldsTemplateService { RequestContextHolder.requestAttributes?.actionName } - private boolean shouldCache() { - // If not explicitly specified, there is no template caching - Boolean cacheDisabled = grailsApplication?.config?.getProperty('grails.plugin.fields.disableLookupCache', Boolean) + private boolean getShouldCache() { + // If not explicitly specified, templates will be cached + Boolean cacheDisabled = grailsApplication?.config?.getProperty(DISABLE_LOOKUP_CACHE, Boolean, Boolean.FALSE) return !cacheDisabled } diff --git a/src/test/groovy/grails/plugin/formfields/TemplateLookupCachingSpec.groovy b/src/test/groovy/grails/plugin/formfields/TemplateLookupCachingSpec.groovy index afe3c6ec..f4a374a5 100644 --- a/src/test/groovy/grails/plugin/formfields/TemplateLookupCachingSpec.groovy +++ b/src/test/groovy/grails/plugin/formfields/TemplateLookupCachingSpec.groovy @@ -1,26 +1,29 @@ package grails.plugin.formfields import grails.plugin.formfields.mock.Person +import grails.plugins.GrailsPluginManager +import grails.testing.services.ServiceUnitTest import org.grails.web.gsp.io.GrailsConventionGroovyPageLocator import org.grails.gsp.io.GroovyPageResourceScriptSource import org.springframework.core.io.ByteArrayResource import spock.lang.* @Issue('https://github.com/grails-fields-plugin/grails-fields/issues/5') -@Stepwise -class TemplateLookupCachingSpec extends BuildsAccessorFactory { +class TemplateLookupCachingSpec extends BuildsAccessorFactory implements ServiceUnitTest { - @Shared def service = new FormFieldsTemplateService() - def mockGroovyPageLocator = Mock(GrailsConventionGroovyPageLocator) - @Shared def beanPropertyAccessorFactory - def person = new Person(name: 'Bart Simpson', password: 'eatmyshorts') + GrailsConventionGroovyPageLocator mockGroovyPageLocator = Mock() + + @Shared + BeanPropertyAccessorFactory beanPropertyAccessorFactory + + Person person = new Person(name: 'Bart Simpson', password: 'eatmyshorts') void setupSpec() { - service.pluginManager = applicationContext.pluginManager beanPropertyAccessorFactory = getFactory() } def setup() { + service.pluginManager = applicationContext.getBean(GrailsPluginManager) service.groovyPageLocator = mockGroovyPageLocator } @@ -43,15 +46,27 @@ class TemplateLookupCachingSpec extends BuildsAccessorFactory { void 'the next time the template is cached'() { given: + def templateResource = new GroovyPageResourceScriptSource('/_fields/person/name/_widget.gsp', new ByteArrayResource('PERSON NAME TEMPLATE'.getBytes('UTF-8'))) + + and: def property = beanPropertyAccessorFactory.accessorFor(person, 'name') - when: + when: 'calling it the first time' def template = service.findTemplate(property, 'input', null, null) - then: + then: 'the template path is correct' template.path == '/_fields/person/name/input' - and: + and: 'the template was found by the service' + 1 * mockGroovyPageLocator.findTemplateByPath(_) >> templateResource + + when: 'calling it the second time' + template = service.findTemplate(property, 'input', null, null) + + then: 'the template path is still correct' + template.path == '/_fields/person/name/input' + + and: 'The mockGroovyPageLocator is only called the first time' 0 * mockGroovyPageLocator.findTemplateByPath(_) } @@ -78,10 +93,10 @@ class TemplateLookupCachingSpec extends BuildsAccessorFactory { and: def property = beanPropertyAccessorFactory.accessorFor(person, 'name') - + when: def template = service.findTemplate(property, 'field', null, null) - + then: template.path == '/_fields/person/name/field' diff --git a/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy b/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy index b7d0066e..f9a1716f 100644 --- a/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy +++ b/src/test/groovy/grails/plugin/formfields/taglib/AbstractFormFieldsTagLibSpec.groovy @@ -11,6 +11,7 @@ import org.grails.scaffolding.model.DomainModelServiceImpl import org.grails.scaffolding.model.property.DomainPropertyFactory import org.grails.scaffolding.model.property.DomainPropertyFactoryImpl import org.grails.spring.beans.factory.InstanceFactoryBean +import org.springframework.context.support.StaticMessageSource import spock.lang.Specification import grails.plugin.formfields.mock.* @@ -31,7 +32,7 @@ abstract class AbstractFormFieldsTagLibSpec extends Specification implements Gra applicationContext.getBean("groovyPagesTemplateEngine").clearPageCache() applicationContext.getBean("groovyPagesTemplateRenderer").clearCache() - messageSource.@messages.clear() // bit of a hack but messages don't get torn down otherwise + (messageSource as StaticMessageSource).messageMap.clear() // bit of a hack but messages don't get torn down otherwise } void setupSpec() { @@ -52,7 +53,7 @@ abstract class AbstractFormFieldsTagLibSpec extends Specification implements Gra } } } - + protected void mockEmbeddedSitemeshLayout(taglib) { taglib.metaClass.applyLayout = { Map attrs, Closure body -> if (attrs.name == '_fields/embedded') { diff --git a/src/test/groovy/grails/plugin/formfields/taglib/TableSpec.groovy b/src/test/groovy/grails/plugin/formfields/taglib/TableSpec.groovy index 247c2aaa..5aacf9e3 100644 --- a/src/test/groovy/grails/plugin/formfields/taglib/TableSpec.groovy +++ b/src/test/groovy/grails/plugin/formfields/taglib/TableSpec.groovy @@ -201,6 +201,26 @@ class TableSpec extends AbstractFormFieldsTagLibSpec implements TagLibUnitTest'"(String order, List expectedTableColumns) { + when: + def table = XML.parse(applyTemplate( + '', + [ + collection: personList, + order: order + ] + )) + def renderedTableColumns = table.thead.tr.th.a.collect { it.text().trim() } + + then: + renderedTableColumns == expectedTableColumns + + where: + order | expectedTableColumns + 'transientText, name' | ['Transient Text', 'Name'] + 'name, transientText' | ['Name', 'Transient Text'] + } void "table tag displays embedded properties by default with toString"() { when: diff --git a/travis/publish_docs.sh b/travis/publish_docs.sh deleted file mode 100755 index a46900bd..00000000 --- a/travis/publish_docs.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash - -git config --global user.name "$GIT_NAME" -git config --global user.email "$GIT_EMAIL" -git config --global credential.helper "store --file=~/.git-credentials" -echo "https://$GH_TOKEN:@github.com" > ~/.git-credentials - -git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git -b gh-pages gh-pages --single-branch > /dev/null -cd gh-pages - -# If this is the master branch then update the snapshot -if [[ $TRAVIS_BRANCH == 'master' ]]; then -mkdir -p snapshot -cp -r ../build/docs/. ./snapshot/ - -git add snapshot/* -fi - -# If there is a tag present then this becomes the latest -if [[ -n $TRAVIS_TAG ]]; then - git rm -rf latest/ - mkdir -p latest - cp -r ../build/docs/. ./latest/ - git add latest/* - - version="$TRAVIS_TAG" - version=${version:1} - majorVersion=${version:0:4} - majorVersion="${majorVersion}x" - - mkdir -p "$version" - cp -r ../build/docs/. "./$version/" - git add "$version/*" - - mkdir -p "$majorVersion" - cp -r ../build/docs/. "./$majorVersion/" - git add "$majorVersion/*" - -fi - -git commit -a -m "Updating docs for Travis build: https://travis-ci.org/$TRAVIS_REPO_SLUG/builds/$TRAVIS_BUILD_ID" -git push origin HEAD -cd .. -rm -rf gh-pages diff --git a/travis/publish_reports.sh b/travis/publish_reports.sh deleted file mode 100755 index dc430bb3..00000000 --- a/travis/publish_reports.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -git config --global user.name "$GIT_NAME" -git config --global user.email "$GIT_EMAIL" -git config --global credential.helper "store --file=~/.git-credentials" -echo "https://$GH_TOKEN:@github.com" > ~/.git-credentials - -git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git -b gh-pages gh-pages --single-branch > /dev/null -cd gh-pages - -mkdir -p report -cp -r ../build/reports/tests/test/* ./report/ - -git add -A ./report/* - -git commit -a -m "Updating reports for Travis build: https://travis-ci.org/$TRAVIS_REPO_SLUG/builds/$TRAVIS_BUILD_ID" -git push origin HEAD -cd .. -rm -rf gh-pages