From a7f52dbb7b17a186a3d2646fb886647f60aea64a Mon Sep 17 00:00:00 2001 From: James Daugherty Date: Tue, 21 Jan 2025 13:01:19 -0500 Subject: [PATCH] Initial Upgrade to Grails 7 & Hibernate 5.6.15.Final --- .github/dependabot.yml | 24 ++ .github/release-drafter.yml | 126 +++++++++ .github/renovate.json | 69 +++++ .github/workflows/gradle.yml | 76 ++++++ .github/workflows/release.yml | 138 ++++++++++ .gitignore | 15 +- .sdkmanrc | 2 + README.md | 7 + application.properties | 1 - build.gradle | 49 ++-- docs/build.gradle | 32 +++ {src => docs/src}/docs/commands.adoc | 0 .../docs/commands/db-reverse-engineer.adoc | 2 +- docs/src/docs/configuration.adoc | 58 ++++ {src => docs/src}/docs/generalUsage.adoc | 38 +-- {src => docs/src}/docs/index.adoc | 0 {src => docs/src}/docs/index.tmpl | 36 +-- {src => docs/src}/docs/introduction.adoc | 0 {src => docs/src}/docs/tutorial.adoc | 50 ++-- examples/mysql/.sdkmanrc | 2 + examples/mysql/build.gradle | 87 ++++++ examples/mysql/gradle.properties | 7 + .../mysql/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + examples/mysql/gradlew | 252 ++++++++++++++++++ examples/mysql/gradlew.bat | 94 +++++++ .../mysql/grails-app/conf/application.yml | 90 +++++++ .../mysql/grails-app/conf/logback-spring.xml | 33 +++ .../grails-app/i18n/messages.properties | 0 .../example/reveng/test/Application.groovy | 23 ++ .../com/example/reveng/test/BootStrap.groovy | 21 ++ examples/mysql/grails-wrapper.jar | Bin 0 -> 5743 bytes examples/mysql/grailsw | 152 +++++++++++ examples/mysql/grailsw.bat | 89 +++++++ examples/mysql/settings.gradle | 1 + .../mysql/src/main/resources}/reveng.sql | 0 gradle.properties | 10 +- gradle/common.gradle | 61 ----- gradle/docs.gradle | 43 +++ gradle/plugin.gradle | 56 ---- gradle/test-config.gradle | 22 ++ gradle/testapp.gradle | 39 --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 252 ++++++++++++++++++ gradlew.bat | 94 +++++++ .../reveng/DbReverseEngineerCommand.groovy | 110 -------- grails-app/conf/BuildConfig.groovy | 28 -- grails-app/conf/logback.groovy | 26 -- plugin/build.gradle | 41 +++ .../reveng/DbReverseEngineerCommand.groovy | 42 +++ .../grails-app}/conf/application.yml | 0 plugin/grails-app/conf/logback-spring.xml | 33 +++ .../DbReverseEngineerGrailsPlugin.groovy | 6 +- .../plugins}/reveng/GrailsCfg2JavaTool.groovy | 11 +- .../reveng/GrailsEntityPOJOClass.groovy | 12 +- .../plugins}/reveng/GrailsJdbcBinder.groovy | 35 +-- .../GrailsJdbcMetadataDescriptor.groovy | 118 ++++++++ .../plugins}/reveng/GrailsPojoExporter.groovy | 4 +- .../GrailsReverseEngineeringStrategy.groovy | 2 +- .../reveng/GrailsTemplateProducer.groovy | 2 +- .../ReverseEngineerProgressListener.groovy | 2 +- .../reveng/ReverseEngineeringEngine.groovy | 30 +-- .../reveng/ReverseEngineeringFactory.groovy | 182 +++++++++++++ settings.gradle | 9 +- src/docs/configuration.adoc | 58 ---- .../GrailsJdbcMetaDataConfiguration.groovy | 33 --- .../grails/plugin/reveng/RevengRunner.groovy | 103 ------- test-app/build.gradle | 26 -- test-app/gradle.properties | 1 - test-app/grails-app/conf/BuildConfig.groovy | 30 --- test-app/grails-app/conf/application.yml | 40 --- test-app/grails-app/conf/logback.groovy | 24 -- .../init/reveng/test/Application.groovy | 10 - version.txt | 1 - 75 files changed, 2389 insertions(+), 795 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/release-drafter.yml create mode 100644 .github/renovate.json create mode 100644 .github/workflows/gradle.yml create mode 100644 .github/workflows/release.yml create mode 100644 .sdkmanrc create mode 100644 README.md delete mode 100644 application.properties create mode 100644 docs/build.gradle rename {src => docs/src}/docs/commands.adoc (100%) rename {src => docs/src}/docs/commands/db-reverse-engineer.adoc (94%) create mode 100644 docs/src/docs/configuration.adoc rename {src => docs/src}/docs/generalUsage.adoc (65%) rename {src => docs/src}/docs/index.adoc (100%) rename {src => docs/src}/docs/index.tmpl (80%) rename {src => docs/src}/docs/introduction.adoc (100%) rename {src => docs/src}/docs/tutorial.adoc (91%) create mode 100644 examples/mysql/.sdkmanrc create mode 100644 examples/mysql/build.gradle create mode 100644 examples/mysql/gradle.properties create mode 100644 examples/mysql/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/mysql/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/mysql/gradlew create mode 100644 examples/mysql/gradlew.bat create mode 100644 examples/mysql/grails-app/conf/application.yml create mode 100644 examples/mysql/grails-app/conf/logback-spring.xml rename {test-app => examples/mysql}/grails-app/i18n/messages.properties (100%) create mode 100644 examples/mysql/grails-app/init/com/example/reveng/test/Application.groovy create mode 100644 examples/mysql/grails-app/init/com/example/reveng/test/BootStrap.groovy create mode 100644 examples/mysql/grails-wrapper.jar create mode 100755 examples/mysql/grailsw create mode 100644 examples/mysql/grailsw.bat create mode 100644 examples/mysql/settings.gradle rename {test-app => examples/mysql/src/main/resources}/reveng.sql (100%) delete mode 100644 gradle/common.gradle create mode 100644 gradle/docs.gradle delete mode 100644 gradle/plugin.gradle create mode 100644 gradle/test-config.gradle delete mode 100644 gradle/testapp.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 grails-app/commands/grails/plugin/reveng/DbReverseEngineerCommand.groovy delete mode 100644 grails-app/conf/BuildConfig.groovy delete mode 100644 grails-app/conf/logback.groovy create mode 100644 plugin/build.gradle create mode 100644 plugin/grails-app/commands/org/grails/plugins/reveng/DbReverseEngineerCommand.groovy rename {grails-app => plugin/grails-app}/conf/application.yml (100%) create mode 100644 plugin/grails-app/conf/logback-spring.xml rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/DbReverseEngineerGrailsPlugin.groovy (92%) rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/GrailsCfg2JavaTool.groovy (78%) rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/GrailsEntityPOJOClass.groovy (97%) rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/GrailsJdbcBinder.groovy (56%) create mode 100644 plugin/src/main/groovy/org/grails/plugins/reveng/GrailsJdbcMetadataDescriptor.groovy rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/GrailsPojoExporter.groovy (95%) rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/GrailsReverseEngineeringStrategy.groovy (99%) rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/GrailsTemplateProducer.groovy (99%) rename {src/main/groovy/grails/plugin => plugin/src/main/groovy/org/grails/plugins}/reveng/ReverseEngineerProgressListener.groovy (96%) rename src/main/groovy/grails/plugin/reveng/Reenigne.groovy => plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringEngine.groovy (81%) create mode 100644 plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringFactory.groovy delete mode 100644 src/docs/configuration.adoc delete mode 100644 src/main/groovy/grails/plugin/reveng/GrailsJdbcMetaDataConfiguration.groovy delete mode 100644 src/main/groovy/grails/plugin/reveng/RevengRunner.groovy delete mode 100644 test-app/build.gradle delete mode 100644 test-app/gradle.properties delete mode 100644 test-app/grails-app/conf/BuildConfig.groovy delete mode 100644 test-app/grails-app/conf/application.yml delete mode 100644 test-app/grails-app/conf/logback.groovy delete mode 100644 test-app/grails-app/init/reveng/test/Application.groovy delete mode 100644 version.txt diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6c7339e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,24 @@ +version: 2 +updates: + - package-ecosystem: gradle + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + target-branch: 5.0.x + labels: + - "type: dependency upgrade" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] + - package-ecosystem: gradle + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 + target-branch: 6.0.x + labels: + - "type: dependency upgrade" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major", "version-update:semver-minor"] \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..76d53db --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,126 @@ +name-template: $RESOLVED_VERSION +tag-template: v$RESOLVED_VERSION +pull-request: + title-templates: + fix: 'πŸ› $TITLE (#$NUMBER)' + feat: 'πŸš€ $TITLE (#$NUMBER)' + default: '$TITLE (#$NUMBER)' +autolabeler: + - label: 'bug' + branch: + - '/fix\/.+/' + title: + - '/fix/i' + - label: 'feature' + branch: + - '/feature\/.+/' + title: + - '/feat/i' + - label: 'documentation' + branch: + - '/docs\/.+/' + title: + - '/docs/i' + - label: 'maintenance' + branch: + - '/(chore|refactor|style|test|ci|perf|build|deps)\/.+/' + title: + - '/(chore|refactor|style|test|ci|perf|build|deps)/i' + - label: 'chore' + branch: + - '/chore\/.+/' + title: + - '/chore/i' + - label: 'refactor' + branch: + - '/refactor\/.+/' + title: + - '/refactor/i' + - label: 'style' + branch: + - '/style\/.+/' + title: + - '/style/i' + - label: 'test' + branch: + - '/test\/.+/' + title: + - '/test/i' + - label: 'ci' + branch: + - '/ci\/.+/' + title: + - '/ci/i' + - label: 'perf' + branch: + - '/perf\/.+/' + title: + - '/perf/i' + - label: 'build' + branch: + - '/build\/.+/' + title: + - '/build/i' + - label: 'deps' + branch: + - '/deps\/.+/' + title: + - '/deps/i' + - label: 'revert' + branch: + - '/revert\/.+/' + title: + - '/revert/i' +categories: + - title: 'πŸš€ Features' + labels: + - 'feat' + - "type: enhancement" + - "type: new feature" + - "type: major" + - title: 'πŸ› Bug Fixes' + labels: + - 'fix' + - "type: improvement" + - "type: bug" + - "type: minor" + - title: 'πŸ“š Documentation' + labels: + - 'docs' + - title: 'πŸ”§ Maintenance' + labels: + - 'chore' + - 'refactor' + - 'style' + - 'test' + - 'ci' + - 'perf' + - 'build' + - 'deps' + - "type: dependency upgrade" + - "dependencies" + - "type: ci" + - "type: build" + - title: 'βͺ Reverts' + labels: + - 'revert' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +version-resolver: + major: + labels: + - 'type: major' + minor: + labels: + - 'type: minor' + patch: + labels: + - 'type: patch' + default: patch +template: | + ## What's Changed + + $CHANGES + + ## Contributors + + $CONTRIBUTORS \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..8d829c0 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,69 @@ +{ + "extends": [ + "config:base" + ], + "labels": ["type: dependency upgrade"], + "packageRules": [ + { + "matchPackagePatterns": ["*"], + "allowedVersions": "!/SNAPSHOT$/" + }, + { + "matchPackagePatterns": [ + "^org\\.codehaus\\.groovy" + ], + "groupName": "groovy monorepo" + }, + { + "matchPackagePatterns": [ + "^org\\.seleniumhq" + ], + "groupName": "selenium monorepo" + }, + { + "matchPackagePatterns": [ + "^org\\.spockframework" + ], + "groupName": "spock framework monorepo" + }, + { + "matchPackageNames": [ + "org.grails:grails-bom", + "org.grails:grails-bootstrap", + "org.grails:grails-codecs", + "org.grails:grails-console", + "org.grails:grails-core", + "org.grails:grails-databinding", + "org.grails:grails-dependencies", + "org.grails:grails-docs", + "org.grails:grails-encoder", + "org.grails:grails-gradle-model", + "org.grails:grails-logging", + "org.grails:grails-plugin-codecs", + "org.grails:grails-plugin-controllers", + "org.grails:grails-plugin-databinding", + "org.grails:grails-plugin-datasource", + "org.grails:grails-plugin-domain-class", + "org.grails:grails-plugin-i18n", + "org.grails:grails-plugin-interceptors", + "org.grails:grails-plugin-mimetypes", + "org.grails:grails-plugin-rest", + "org.grails:grails-plugin-services", + "org.grails:grails-plugin-url-mappings", + "org.grails:grails-plugin-url-validation", + "org.grails:grails-shell", + "org.grails:grails-spring", + "org.grails:grails-test", + "org.grails:grails-validation", + "org.grails:grails-web", + "org.grails:grails-web-boot", + "org.grails:grails-web-common", + "org.grails:grails-web-databinding", + "org.grails:grails-web-fileupload", + "org.grails:grails-web-mvc", + "org.grails:grails-web-url-mappings" + ], + "groupName": "grails monorepo" + } + ] +} \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..1699467 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,76 @@ +name: "Java CI" +on: + push: + branches: + - '[5-9].[0-9].x' + pull_request: + types: [ opened, reopened, synchronize ] + workflow_dispatch: +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + java: ['17', '21'] + steps: + - name: "πŸ“₯ Checkout the repository" + uses: actions/checkout@v4 + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: 'liberica' + java-version: ${{ matrix.java }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + - name: "πŸ”¨ Run Base Tests" + run: ./gradlew check --continue + - name: "πŸ”¨ Run Example App to confirm it does not fail" + working-directory: examples/mysql + run: ./gradlew bootRun + publish: + if: github.event_name == 'push' + needs: build + runs-on: ubuntu-latest + permissions: + contents: write # publishing docs + pages: write + steps: + - name: "πŸ“₯ Checkout the repository" + uses: actions/checkout@v4 + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: 'liberica' + java-version: '17' + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + - name: Read project version + id: version + run: | + # Extract the version from the property file. + version=$(grep '^projectVersion=' gradle.properties | cut -d= -f2) + echo "version=$version" >> $GITHUB_OUTPUT + shell: bash + - name: "πŸ“€ Publish to Snapshot" + if: ${{ success() && endsWith( steps.version.outputs.version, '-SNAPSHOT' ) }} + env: + MAVEN_PUBLISH_USERNAME: ${{ secrets.MAVEN_PUBLISH_USERNAME }} + MAVEN_PUBLISH_PASSWORD: ${{ secrets.MAVEN_PUBLISH_PASSWORD }} + MAVEN_PUBLISH_URL: ${{ secrets.MAVEN_PUBLISH_SNAPSHOT_URL }} + GRAILS_PUBLISH_RELEASE: "false" + working-directory: ./plugin + run: ../gradlew publish + - name: "πŸ“œ Generate User Guide Documentation" + if: success() + run: ./gradlew docs + - name: "πŸš€ Publish to Github Pages" + if: ${{ success() && endsWith( steps.version.outputs.version, '-SNAPSHOT' ) }} + uses: grails/github-pages-deploy-action@grails + env: + BRANCH: gh-pages + COMMIT_EMAIL: ${{ env.GIT_USER_EMAIL }} + COMMIT_NAME: ${{ env.GIT_USER_NAME }} + FOLDER: build/docs + GH_TOKEN: ${{ secrets.GH_TOKEN }} + TARGET_REPOSITORY: ${{ github.repository }} + DOC_FOLDER: gh-pages \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d9b1d75 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,138 @@ +name: "Release" +on: + release: + types: [published] +env: + GIT_USER_NAME: 'grails-build' + GIT_USER_EMAIL: 'grails-build@users.noreply.github.com' +jobs: + publish: + permissions: + contents: write # to update gradle.properties + runs-on: ubuntu-latest + outputs: + release_version: ${{ steps.release_version.outputs.value }} + target_branch: ${{ steps.extract_branch.outputs.value }} + steps: + - name: "πŸ“₯ Checkout the repository" + uses: actions/checkout@v4 + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: 'liberica' + java-version: '17' + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + - name: "πŸ“ Store the target branch" + id: extract_branch + run: | + echo "Determining Target Branch" + TARGET_BRANCH=${GITHUB_REF#refs/heads/} + echo $TARGET_BRANCH + echo "value=${TARGET_BRANCH}" >> $GITHUB_OUTPUT + - name: "πŸ“Set the current release version" + id: release_version + run: echo "value=${GITHUB_REF:11}" >> $GITHUB_OUTPUT + - name: "βš™οΈ Set release in gradle.properties & tag" + uses: grails/github-actions/pre-release@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: "🧩 Run Assemble" + if: success() + id: assemble + run: ./gradlew assemble + - name: "πŸ” Generate secring file" + if: success() + env: + SECRING_FILE: ${{ secrets.SECRING_FILE }} + run: echo $SECRING_FILE | base64 -d > ${{ github.workspace }}/secring.gpg + - name: "πŸš€ Publish to Sonatype OSSRH" + if: success() + id: publish + env: + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: ${{ secrets.NEXUS_PUBLISH_RELEASE_URL }} + NEXUS_PUBLISH_STAGING_PROFILE_ID: ${{ secrets.NEXUS_PUBLISH_STAGING_PROFILE_ID }} + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} + SECRING_FILE: ${{ secrets.SECRING_FILE }} + GRAILS_PUBLISH_RELEASE: "true" + run: > + ./gradlew + -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg + publishToSonatype + closeSonatypeStagingRepository + release: + # enable environment to allow a gated release + #environment: production + needs: publish + runs-on: ubuntu-latest + permissions: + contents: write # allow snapshot version changes + steps: + - name: "πŸ“₯ Checkout repository" + uses: actions/checkout@v4 + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: liberica + java-version: 17 + - name: "πŸ“₯ Checkout repository" + uses: actions/checkout@v4 + with: + ref: v${{ needs.publish.outputs.release_version }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + - name: "πŸ†Nexus Staging Close And Release" + if: success() + env: + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: ${{ secrets.NEXUS_PUBLISH_RELEASE_URL }} + NEXUS_PUBLISH_STAGING_PROFILE_ID: ${{ secrets.NEXUS_PUBLISH_STAGING_PROFILE_ID }} + GRAILS_PUBLISH_RELEASE: "true" + run: > + ./gradlew + findSonatypeStagingRepository + releaseSonatypeStagingRepository + - name: "βš™οΈBack to snapshot" + if: success() + uses: grails/github-actions/post-release@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + docs: + needs: [publish, release] + runs-on: ubuntu-latest + permissions: + contents: write # docs publishing + pages: write + steps: + - name: "πŸ“₯ Checkout the repository" + uses: actions/checkout@v4 + with: + ref: v${{ needs.publish.outputs.release_version }} + - name: "β˜•οΈ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: 'liberica' + java-version: '17' + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + - name: "πŸ“œ Generate User Guide Documentation" + run: ./gradlew docs + - name: "πŸš€ Publish to Github Pages" + if: success() + uses: grails/github-pages-deploy-action@grails + env: + SKIP_SNAPSHOT: ${{ contains(needs.publish.outputs.release_version, 'M') }} + # if multiple releases are being done, this is the last branch - 1 version + #SKIP_LATEST: ${{ !startsWith(needs.publish.outputs.target_branch, '6.2') }} + TARGET_REPOSITORY: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: gh-pages + FOLDER: build/docs + DOC_FOLDER: gh-pages + COMMIT_EMAIL: ${{ env.GIT_USER_EMAIL }} + COMMIT_NAME: ${{ env.GIT_USER_NAME }} + VERSION: ${{ needs.publish.outputs.release_version }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index c550c0d..152560e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,15 @@ *.iml *.ipr *.iws -*.log -*.sublime-workspace -.asscache .DS_Store +.classpath +.factorypath .gradle .idea -build -cobertura.ser -/test-app/grails-app/domain +.project +.settings +Thumbs.db +build/ +out/ +target/ +examples/**/domain \ No newline at end of file diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..96e716d --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,2 @@ +# Enable auto-env through the sdkman_auto_env config - https://sdkman.io/usage#env +java=17.0.12-librca diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7ada44 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +DB Reverse Engineering Plugin +=== + +The DB Reverse Engineering plugin reads database table information using JDBC and uses the schema data to create domain classes. This is a complex problem and the plugin is unlikely to get things 100% correct. But it should save you a lot of work and hopefully not require too much tweaking after the domain classes are generated. + +The plugin uses the [Hibernate Tools](http://hibernate.org/tools/) library, with custom code to generate GORM domain classes instead of Java POJOs. + diff --git a/application.properties b/application.properties deleted file mode 100644 index e3fed3f..0000000 --- a/application.properties +++ /dev/null @@ -1 +0,0 @@ -app.grails.version=7.0.0-SNAPSHOT diff --git a/build.gradle b/build.gradle index afb0853..a71738d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,36 +1,39 @@ buildscript { - ext { - grailsVersion = project.grailsVersion - } repositories { - mavenLocal() - maven { url 'https://repo.grails.org/grails/core' } - jcenter() + maven { url "https://repo.grails.org/grails/core/" } } dependencies { - classpath "org.grails:grails-gradle-plugin:$grailsVersion" - classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2' - classpath 'org.asciidoctor:asciidoctorj-epub3:1.5.0-alpha.4' - classpath 'org.asciidoctor:asciidoctorj-pdf:1.5.0-alpha.9' + classpath platform("org.grails:grails-bom:${grailsVersion}") + classpath "org.grails:grails-gradle-plugin" + classpath "com.adarshr.test-logger:com.adarshr.test-logger.gradle.plugin:4.0.0" } } -plugins { - id 'io.spring.dependency-management' - id 'com.jfrog.bintray' version '1.2' +allprojects { + repositories { + maven { url "https://repo.grails.org/grails/core" } + } } -apply from: 'gradle/plugin.gradle' - -dependencies { +version project.projectVersion +group "org.grails.plugins" - provided 'org.grails:grails-core' +subprojects { + version project.projectVersion + group "org.grails.plugins" - compile 'org.hibernate:hibernate-tools:4.3.1.Final', { - ['ant', 'common', 'freemarker', 'org.eclipse.jdt.core', 'runtime', 'text'].each { exclude module: it } + if (project.name == 'grails-db-reverse-engineer') { + apply plugin: "org.grails.grails-publish" + grailsPublish { + githubSlug = 'grails-plugins/grails-db-reverse-engineer' + license { + name = 'Apache-2.0' + } + title = 'DB Reverse Engineering Plugin' + desc = 'Given a database, this plugin generates Grails domain classes from it.' + developers = [burtbeckwith: "Burt Beckwith"] + } } - - compile 'org.freemarker:freemarker:2.3.23' - - compile 'org.hibernate:hibernate-core:4.3.10.Final' } + +apply from: rootProject.file("gradle/docs.gradle") diff --git a/docs/build.gradle b/docs/build.gradle new file mode 100644 index 0000000..53ea291 --- /dev/null +++ b/docs/build.gradle @@ -0,0 +1,32 @@ +import org.asciidoctor.gradle.jvm.AsciidoctorTask + +plugins { + id 'org.asciidoctor.jvm.convert' version "4.0.3" +} + +def asciidoctorAttributes = [ + 'source-highlighter': 'coderay', + toc : 'left', + toclevels : '2', + 'toc-title' : 'Table of Contents', + icons : 'font', + id : project.name + ':' + project.version, + idprefix : '', + idseparator : '-', + version : project.version, + 'project-version' : project.version, + sourcedir : "${rootProject.allprojects.find { it.name == 'grails-db-reverse-engineer' }.projectDir}/src/main/groovy", +] + +tasks.named('asciidoctor', AsciidoctorTask) { AsciidoctorTask it -> + it.jvm { + jvmArgs("--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED", "--add-opens", "java.base/java.io=ALL-UNNAMED") + } + + it.baseDirFollowsSourceDir() + it.sourceDir project.file('src/docs') + it.sources { include 'index.adoc' } + it.outputDir = project.buildDir.toPath().resolve('docs').toFile() + it.options doctype: 'book' + it.attributes asciidoctorAttributes +} diff --git a/src/docs/commands.adoc b/docs/src/docs/commands.adoc similarity index 100% rename from src/docs/commands.adoc rename to docs/src/docs/commands.adoc diff --git a/src/docs/commands/db-reverse-engineer.adoc b/docs/src/docs/commands/db-reverse-engineer.adoc similarity index 94% rename from src/docs/commands/db-reverse-engineer.adoc rename to docs/src/docs/commands/db-reverse-engineer.adoc index ad040f2..6453354 100644 --- a/src/docs/commands/db-reverse-engineer.adoc +++ b/docs/src/docs/commands/db-reverse-engineer.adoc @@ -10,5 +10,5 @@ Note that running the command like a typical Grails command (i.e. `grails db-rev .Example .... -$ ./gradlew dbReverseEngineer +$ ./gradlew runCommand dbReverseEngineer .... diff --git a/docs/src/docs/configuration.adoc b/docs/src/docs/configuration.adoc new file mode 100644 index 0000000..cad68e6 --- /dev/null +++ b/docs/src/docs/configuration.adoc @@ -0,0 +1,58 @@ +[[configuration]] +== Configuration + +There are several configuration options for the plugin. + +=== Core properties + +These configuration options are the ones you're most likely need to set. They include the package name of the generated files (`org.grails.plugins.reveng.packageName`), and information about which side of a many-to-many is the "`belongsTo`" side (`org.grails.plugins.reveng.manyToManyBelongsTos`). + +[width="100%",options="header"] +|==================== +| *Property* | *Default* | *Meaning* +| org.grails.plugins.reveng.packageName | application name | package name for the generated domain classes +| org.grails.plugins.reveng.manyToManyBelongsTos | none | a Map of join table name -> belongsTo table name to specify which of the two domain classes is the 'belongsTo' side of the relationship +|==================== + +=== Inclusion/exclusion properties + +These configuration options let you define which tables and columns to include in processing. By default all tables and columns are processed. + +If you specify tables to include (`g.p.r.includeTables`, `g.p.r.includeTableRegexes`, and/or `g.p.r.includeTableAntPatterns`) then only those will be used. If you specify any of these options then all of the table exclusion options are ignored; this is useful when you have already run the reverse-engineer command but want to re-run it for only a subset of tables. + +If you specify tables to exclude (`g.p.r.excludeTables`, `g.p.r.excludeTableRegexes`, and/or `g.p.r.excludeTableAntPatterns`) then any matching tables will be ignored. + +You can also specify columns to exclude (`g.p.r.excludeColumns`, `g.p.r.excludeColumnRegexes`, and/or `g.p.r.excludeColumnAntPatterns`) and any matching columns will be ignored. These options can be used with table include rules or table exclude rules - any tables that are included or not excluded will have their columns included or excludede based on these rules. + +One addition property, `org.grails.plugins.reveng.manyToManyTables`, doesn't affect whether a table is processed, but rather determines whether a table that won't look like a many-to-many table to the Hibernate Tools library (because it has more than two columns) is considered a many-to-many table. + +[width="100%",options="header"] +|==================== +| *Property* | *Default* | *Meaning* +| org.grails.plugins.reveng.includeTables | none | a List of table names to include for processing +| org.grails.plugins.reveng.includeTableRegexes | none | a List of table name regex patterns to include for processing +| org.grails.plugins.reveng.includeTableAntPatterns | none | a List of table name Ant-style patterns to include for processing +| org.grails.plugins.reveng.excludeTables | none | a List of table names to exclude from processing +| org.grails.plugins.reveng.excludeTableRegexes | none | a List of table name regex patterns to exclude from processing +| org.grails.plugins.reveng.excludeTableAntPatterns | none | a List of table name Ant-style patterns to exclude from processing +| org.grails.plugins.reveng.excludeColumns | none | a Map of table name -> List of column names to ignore +| org.grails.plugins.reveng.excludeColumnRegexes | none | a Map of table name -> List of column name regex patterns to ignore +| org.grails.plugins.reveng.excludeColumnAntPatterns | none | a Map of table name -> List of column name Ant-style patterns to ignore +| org.grails.plugins.reveng.manyToManyTables | none | a List of table names that should be considered many-to-many join tables; needed for join tables that have more columns than the two foreign keys +|==================== + +=== Other properties + +These remaining configuration options allow you to specify the folder where the domain classes are generated (`g.p.r.destDir`), whether to overwrite existing classes (`g.p.r.overwriteExisting`) and non-standard 'version' column names (`g.p.r.versionColumns`). + +There are also properties to set the default schema name (`g.p.r.defaultSchema`) and catalog (`g.p.r.defaultCatalog`) name which are useful when working with Oracle. + +[width="100%",options="header"] +|==================== +| *Property* | *Default* | *Meaning* +| org.grails.plugins.reveng.destDir | 'grails-app/domain' | destination folder for the generated classes, relative to the project root +| org.grails.plugins.reveng.versionColumns | none | a Map of table name -> version column name, for tables with an optimistic locking column that's not named 'version' +| org.grails.plugins.reveng.overwriteExisting | `true` | whether to overwrite existing domain classes +| org.grails.plugins.reveng.defaultSchema | none | the default database schema name +| org.grails.plugins.reveng.defaultCatalog | none | the default database catalog name +|==================== diff --git a/src/docs/generalUsage.adoc b/docs/src/docs/generalUsage.adoc similarity index 65% rename from src/docs/generalUsage.adoc rename to docs/src/docs/generalUsage.adoc index a28ea7d..5fb9e76 100644 --- a/src/docs/generalUsage.adoc +++ b/docs/src/docs/generalUsage.adoc @@ -10,7 +10,7 @@ The core of the plugin is the <> command. There are too man Note that running the command like a typical Grails command (i.e. `grails db-reverse-engineer`) will fail due to a bug when parsing the `grails.factories` file in the plugin jar's `META-INF` folder. This is not a problem however since the simple workaround is to run it as a Gradle task: .... -$ ./gradlew dbReverseEngineer +$ ./gradlew runCommand dbReverseEngineer .... === Environments @@ -18,79 +18,79 @@ $ ./gradlew dbReverseEngineer You can choose which database to read from by specifying the environment when running the command. For example to use the development environment settings, just run .... -$ ./gradlew dbReverseEngineer +$ ./gradlew runCommand dbReverseEngineer .... To use the production environment settings, run .... -$ ./gradlew -Dgrails.env=prod dbReverseEngineer +$ ./grailsw -Dgrails.env=prod dbReverseEngineer .... And if you want to use a custom 'staging' environment configured in `DataSource.groovy`, run .... -$ ./gradlew -Dgrails.env=staging dbReverseEngineer +$ ./grailsw -Dgrails.env=staging dbReverseEngineer .... === Re-running the reverse engineering script If you have new or changed tables you can re-run the <> command to pick up changes and additions. This is not an incremental process though, so existing classes will be overwritten and you will lose any changes you made since the last run. But it's simple to define which tables to include or exclude. -As described in <> section, you can use a combination of the `grails.plugin.reveng.includeTables`, `grails.plugin.reveng.includeTableRegexes`, `grails.plugin.reveng.includeTableAntPatterns`, `grails.plugin.reveng.excludeTables`, `grails.plugin.reveng.excludeTableRegexes`, and `grails.plugin.reveng.excludeTableAntPatterns` properties to define which tables to include or exclude. +As described in <> section, you can use a combination of the `org.grails.plugins.reveng.includeTables`, `org.grails.plugins.reveng.includeTableRegexes`, `org.grails.plugins.reveng.includeTableAntPatterns`, `org.grails.plugins.reveng.excludeTables`, `org.grails.plugins.reveng.excludeTableRegexes`, and `org.grails.plugins.reveng.excludeTableAntPatterns` properties to define which tables to include or exclude. -By default all tables are included, and the plugin assumes you're more likely to exclude than include. So you can specify one or more table names to explicitly exclude using `grails.plugin.reveng.excludeTables`, one or more regex patterns for exclusion using `grails.plugin.reveng.excludeTableRegexes`, and one or more Ant-style patterns for exclusion using `grails.plugin.reveng.excludeTableAntPatterns`. +By default all tables are included, and the plugin assumes you're more likely to exclude than include. So you can specify one or more table names to explicitly exclude using `org.grails.plugins.reveng.excludeTables`, one or more regex patterns for exclusion using `org.grails.plugins.reveng.excludeTableRegexes`, and one or more Ant-style patterns for exclusion using `org.grails.plugins.reveng.excludeTableAntPatterns`. For example, using this configuration [source,java] ---- -grails.plugin.reveng.excludeTables = ['clickstream', 'error_log'] -grails.plugin.reveng.excludeTableRegexes = ['temp.+'] -grails.plugin.reveng.excludeTableAntPatterns = ['audit_*'] +org.grails.plugins.reveng.excludeTables = ['clickstream', 'error_log'] +org.grails.plugins.reveng.excludeTableRegexes = ['temp.+'] +org.grails.plugins.reveng.excludeTableAntPatterns = ['audit_*'] ---- you would process all tables except `clickstream` and `error_log`, and any tables that start with 'temp' (e.g. `tempPerson`, `tempOrganization`, etc.) and any tables that start with 'audit_' (e.g. 'audit_orders', 'audit_order_items', etc.) -If you only want to include one or a few tables, it's more convenient to specify inclusion rules rather than exclusion rules, so you use `grails.plugin.reveng.includeTables`, `grails.plugin.reveng.includeTableRegexes`, and `grails.plugin.reveng.includeTableAntPatterns` for that. If any of these properties are set, the table exclusion rules are ignored. +If you only want to include one or a few tables, it's more convenient to specify inclusion rules rather than exclusion rules, so you use `org.grails.plugins.reveng.includeTables`, `org.grails.plugins.reveng.includeTableRegexes`, and `org.grails.plugins.reveng.includeTableAntPatterns` for that. If any of these properties are set, the table exclusion rules are ignored. For example, using this configuration [source,java] ---- -grails.plugin.reveng.includeTables = ['person', 'organization'] +org.grails.plugins.reveng.includeTables = ['person', 'organization'] ---- -you would process (or re-process) just the `person` and `organization` tables. You can also use The `grails.plugin.reveng.includeTableRegexes` and `grails.plugin.reveng.includeTableAntPatterns` properties to include tables based on patterns. +you would process (or re-process) just the `person` and `organization` tables. You can also use The `org.grails.plugins.reveng.includeTableRegexes` and `org.grails.plugins.reveng.includeTableAntPatterns` properties to include tables based on patterns. You can further customize the process by specifying which columns to exclude per-table. For example, this configuration [source,java] ---- -grails.plugin.reveng.excludeColumns = ['some_table': ['col1', 'col2'], +org.grails.plugins.reveng.excludeColumns = ['some_table': ['col1', 'col2'], 'another_table': ['another_column']] ---- will exclude columns `col1` and `col2` from table `some_table`, and column `another_column` from table `another_table`. -You can also use the `grails.plugin.reveng.excludeColumnRegexes` and `grails.plugin.reveng.excludeColumnAntPatterns` properties to define patterns for columns to exclude. +You can also use the `org.grails.plugins.reveng.excludeColumnRegexes` and `org.grails.plugins.reveng.excludeColumnAntPatterns` properties to define patterns for columns to exclude. === Destination folder By default the domain classes are generated under the `grails-app/domain` folder in the package specified. But you can override the destination, for example if you're re-running the process and want to compare the new classes with the previous ones. -By default the `db-reverse-engineer` script will overwrite existing classes. You can set the `grails.plugin.reveng.overwriteExisting` property to `false` to override this behavior and not overwrite existing files. +By default the `db-reverse-engineer` script will overwrite existing classes. You can set the `org.grails.plugins.reveng.overwriteExisting` property to `false` to override this behavior and not overwrite existing files. === Many-to-many tables -Typically many-to-many relationships are implemented using a join table which contains just two columns which are foreign keys referring to the two related tables. It's possible for join tables to have extra columns though, and this will cause problems when trying to infer relationships. By default Hibernate will only consider tables that have two foreign key columns to be join tables. To get the script to correctly use join tables with extra columns, you can specify the table names with the `grails.plugin.reveng.manyToManyTables` property. This is demonstrated in the <>. +Typically many-to-many relationships are implemented using a join table which contains just two columns which are foreign keys referring to the two related tables. It's possible for join tables to have extra columns though, and this will cause problems when trying to infer relationships. By default Hibernate will only consider tables that have two foreign key columns to be join tables. To get the script to correctly use join tables with extra columns, you can specify the table names with the `org.grails.plugins.reveng.manyToManyTables` property. This is demonstrated in the <>. -Another problem with many-to-many relationships is that one of the two GORM classes needs to be the 'owning' side and the other needs to be the 'owned' side, but this cannot be reliably inferred from the database. Both classes need a `hasMany` declaration, but the 'owned' domain class also needs a `belongsTo` declaration. So all of your many-to-many related tables need to have the tables that will create the `belongsTo` classes specified in the `grails.plugin.reveng.manyToManyBelongsTos` property. This is demonstrated in the <>. +Another problem with many-to-many relationships is that one of the two GORM classes needs to be the 'owning' side and the other needs to be the 'owned' side, but this cannot be reliably inferred from the database. Both classes need a `hasMany` declaration, but the 'owned' domain class also needs a `belongsTo` declaration. So all of your many-to-many related tables need to have the tables that will create the `belongsTo` classes specified in the `org.grails.plugins.reveng.manyToManyBelongsTos` property. This is demonstrated in the <>. === Optimistic locking columns -Hibernate assumes that columns used for optimistic lock detection are called `version`. If you have customized one or more column names, you can direct the script about what the custom names are with the `grails.plugin.reveng.versionColumns` property. This is demonstrated in the <>. +Hibernate assumes that columns used for optimistic lock detection are called `version`. If you have customized one or more column names, you can direct the script about what the custom names are with the `org.grails.plugins.reveng.versionColumns` property. This is demonstrated in the <>. === Logging -All of the plugin classes are in the `grails.plugin.reveng` package, so you can configure that package for logging to see generated messages. +All of the plugin classes are in the `org.grails.plugins.reveng` package, so you can configure that package for logging to see generated messages. diff --git a/src/docs/index.adoc b/docs/src/docs/index.adoc similarity index 100% rename from src/docs/index.adoc rename to docs/src/docs/index.adoc diff --git a/src/docs/index.tmpl b/docs/src/docs/index.tmpl similarity index 80% rename from src/docs/index.tmpl rename to docs/src/docs/index.tmpl index 47bc4aa..5931175 100644 --- a/src/docs/index.tmpl +++ b/docs/src/docs/index.tmpl @@ -59,36 +59,26 @@ img { - - Fork me on GitHub - -

Grails Database Reverse Engineering Plugin

- - - - - - - - - - - - - - -
Version@VERSION@ -
Grails Version3.0.0 > *
AuthorBurt Beckwith

-

Current Documentation (Grails 3+)

+

Snapshot Documentation

+ + +

Current Documentation (Grails 7+)

+ + +

Documentation (Grails 3)

Documentation (version 4.0.x, Grails 2.x)

diff --git a/src/docs/introduction.adoc b/docs/src/docs/introduction.adoc similarity index 100% rename from src/docs/introduction.adoc rename to docs/src/docs/introduction.adoc diff --git a/src/docs/tutorial.adoc b/docs/src/docs/tutorial.adoc similarity index 91% rename from src/docs/tutorial.adoc rename to docs/src/docs/tutorial.adoc index 808d948..dc7a5c2 100644 --- a/src/docs/tutorial.adoc +++ b/docs/src/docs/tutorial.adoc @@ -23,7 +23,7 @@ buildscript { ... dependencies { ... - classpath 'org.grails.plugins:db-reverse-engineer:{project-version}' + classpath 'org.grails.plugins:grails-db-reverse-engineer:{project-version}' ... } } @@ -36,7 +36,7 @@ and in the main `dependencies` block: ---- dependencies { ... - compile ':db-reverse-engineer:4.0.0' + implementation 'org.grails.plugins:grails-db-reverse-engineer:{project-version}' ... } ---- @@ -59,8 +59,7 @@ Set these property values in the `development` section of `grails-app/conf/appli dataSource: ... dbCreate: none - dialect: org.hibernate.dialect.MySQL5InnoDBDialect - driverClassName: com.mysql.jdbc.Driver + driverClassName: com.mysql.cj.jdbc.Driver password: reveng url: jdbc:mysql://localhost/reveng username: reveng @@ -74,7 +73,7 @@ Also add a dependency for the MySQL JDBC driver in `build.gradle`: ---- dependencies { ... - runtime 'mysql:mysql-connector-java:5.1.38' + runtimeOnly 'com.mysql:mysql-connector-j' ... } ---- @@ -208,20 +207,27 @@ CREATE TABLE thing ( === Configure the reverse engineering process. -Add these configuration options to `grails-app/conf/application.groovy`: +Add these configuration options to `grails-app/conf/application.yml`: -[source,java] +[source,yaml] ---- -grails.plugin.reveng.packageName = 'com.revengtest' -grails.plugin.reveng.versionColumns = [other: 'nonstandard_version_name'] -grails.plugin.reveng.manyToManyTables = ['user_role'] -grails.plugin.reveng.manyToManyBelongsTos = ['user_role': 'role', 'author_books': 'book'] +grails: + plugin: + reveng: + packageName: 'com.example.reveng.test' + versionColumns: + other: 'nonstandard_version_name' + manyToManyTables: + - user_role + manyToManyBelongsTos: + user_role: role + author_books: book ---- === Run the db-reverse-engineer command. ---- -$ ./gradlew dbReverseEngineer +$ ./gradlew runCommand dbReverseEngineer ---- === Look at the generated domain classes. @@ -282,7 +288,7 @@ class Book { } ---- -`Book` has the line `static belongsTo = Author` because we specified this in `application.groovy` with the `grails.plugin.reveng.manyToManyBelongsTos` property. +`Book` has the line `static belongsTo = Author` because we specified this in `application.groovy` with the `org.grails.plugins.reveng.manyToManyBelongsTos` property. ==== Compos domain class. @@ -303,8 +309,8 @@ and it generates this domain class: [source,java] ---- -import org.apache.commons.lang.builder.EqualsBuilder -import org.apache.commons.lang.builder.HashCodeBuilder +import org.apache.commons.lang3.builder.EqualsBuilder +import org.apache.commons.lang3.builder.HashCodeBuilder class Compos implements Serializable { @@ -428,7 +434,7 @@ class Visit { ==== Other domain class. -The `other` table has a string primary key, and an optimistic locking column that's not named `version`. Since we configured this with the `grails.plugin.reveng.versionColumns` property, the column is resolved correctly: +The `other` table has a string primary key, and an optimistic locking column that's not named `version`. Since we configured this with the `org.grails.plugins.reveng.versionColumns` property, the column is resolved correctly: [source,sql] ---- @@ -491,7 +497,7 @@ CREATE TABLE user_role ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ---- -The `user_role` table has an extra column (`date_updated`) and would be ignored by default, but since we configured it with the `grails.plugin.reveng.manyToManyTables` property it's resolved correctly: +The `user_role` table has an extra column (`date_updated`) and would be ignored by default, but since we configured it with the `org.grails.plugins.reveng.manyToManyTables` property it's resolved correctly: [source,java] ---- @@ -573,27 +579,27 @@ Add a new column to the `thing` table: alter table thing add new_column boolean; ---- -We'll re-run the command but need to configure it to generate the updated domain class in a different directory from the default so we can compare with the original. To configure this, set the value of the `grails.plugin.reveng.destDir` property in `grails-app/conf/application.groovy`: +We'll re-run the command but need to configure it to generate the updated domain class in a different directory from the default so we can compare with the original. To configure this, set the value of the `org.grails.plugins.reveng.destDir` property in `grails-app/conf/application.groovy`: [source,java] ---- -grails.plugin.reveng.destDir = 'temp_reverse_engineer' +org.grails.plugins.reveng.destDir = 'temp_reverse_engineer' ---- Also change the configuration to only include the `thing` table: [source,java] ---- -grails.plugin.reveng.includeTables = ['thing'] +org.grails.plugins.reveng.includeTables = ['thing'] ---- Re-run the db-reverse-engineer command: ---- -$ ./gradlew dbReverseEngineer +$ ./gradlew runCommand dbReverseEngineer ---- -The command will generate this domain class in the temp_reverse_engineer/com/revengtest folder: +The command will generate this domain class in the temp_reverse_engineer/com/reveng/test folder: [source,java] ---- diff --git a/examples/mysql/.sdkmanrc b/examples/mysql/.sdkmanrc new file mode 100644 index 0000000..96e716d --- /dev/null +++ b/examples/mysql/.sdkmanrc @@ -0,0 +1,2 @@ +# Enable auto-env through the sdkman_auto_env config - https://sdkman.io/usage#env +java=17.0.12-librca diff --git a/examples/mysql/build.gradle b/examples/mysql/build.gradle new file mode 100644 index 0000000..a86f182 --- /dev/null +++ b/examples/mysql/build.gradle @@ -0,0 +1,87 @@ +buildscript { + repositories { + maven { url "https://repo.grails.org/grails/core" } + } + dependencies { + classpath platform("org.grails:grails-bom:${grailsVersion}") + classpath "org.grails:grails-gradle-plugin" + classpath "com.adarshr.test-logger:com.adarshr.test-logger.gradle.plugin:4.0.0" + // TODO: can't put this on the classpath due to groovy 4 (the build/grails) vs 3 (gradle) + // classpath "org.grails.plugins:grails-db-reverse-engineer" // so the grails command is findable + // only needed if we want to extract the ddl + // classpath "org.grails.plugins:hibernate5" + } +} + +plugins { + id "groovy" + id "war" + id "idea" + id "application" +} + +repositories { + maven { url "https://repo.grails.org/grails/core" } +} + +apply plugin: "org.grails.grails-web" + +version projectVersion +group "com.reveng.test" + +project.compileJava.options.release = 17 + +dependencies { + profile "org.grails.profiles:web" + + implementation platform("org.grails:grails-bom:$grailsVersion") + implementation "org.grails:grails-core" + implementation "org.grails:grails-logging" + implementation "org.grails:grails-plugin-databinding" + implementation "org.grails:grails-plugin-i18n" + implementation "org.grails:grails-plugin-interceptors" + implementation "org.grails:grails-plugin-rest" + implementation "org.grails:grails-plugin-services" + implementation "org.grails:grails-plugin-url-mappings" + implementation "org.grails:grails-web-boot" + implementation "org.grails.plugins:gsp" + implementation "org.grails.plugins:hibernate5" + implementation "org.grails.plugins:scaffolding" + implementation "org.springframework.boot:spring-boot-autoconfigure" + implementation "org.springframework.boot:spring-boot-starter" + implementation "org.springframework.boot:spring-boot-starter-actuator" + implementation "org.springframework.boot:spring-boot-starter-logging" + implementation "org.springframework.boot:spring-boot-starter-tomcat" + implementation "org.springframework.boot:spring-boot-starter-validation" + console "org.grails:grails-console" + runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails" + runtimeOnly "org.apache.tomcat:tomcat-jdbc" + runtimeOnly "org.fusesource.jansi:jansi" + integrationTestImplementation testFixtures("org.grails.plugins:geb") + testImplementation "org.grails:grails-gorm-testing-support" + testImplementation "org.grails:grails-web-testing-support" + testImplementation "org.spockframework:spock-core" + testImplementation "org.testcontainers:spock" + testImplementation "org.testcontainers:testcontainers" + + implementation "org.testcontainers:mysql" + runtimeOnly 'com.mysql:mysql-connector-j' + + implementation 'org.grails.plugins:grails-db-reverse-engineer' +} + +apply from: rootProject.layout.projectDirectory.file('../../gradle/test-config.gradle') + +gradle.taskGraph.whenReady { taskGraph -> + if (taskGraph.allTasks.any { it.name == 'bootRun' }) { + logger.lifecycle("Removing generated domain classes since `bootRun` is being executed.") + def domains = project.layout.projectDirectory.dir('grails-app/domain').asFile + if(domains.exists()) { + domains.deleteDir() + } + } +} + +//bootRun { +// jvmArgs = ['-Xdebug', '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005'] +//} \ No newline at end of file diff --git a/examples/mysql/gradle.properties b/examples/mysql/gradle.properties new file mode 100644 index 0000000..b8d5404 --- /dev/null +++ b/examples/mysql/gradle.properties @@ -0,0 +1,7 @@ +projectVersion=0.0.1 +grailsVersion=7.0.0-SNAPSHOT + +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M \ No newline at end of file diff --git a/examples/mysql/gradle/wrapper/gradle-wrapper.jar b/examples/mysql/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/examples/mysql/gradle/wrapper/gradle-wrapper.properties b/examples/mysql/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e2847c8 --- /dev/null +++ b/examples/mysql/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/mysql/gradlew b/examples/mysql/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/examples/mysql/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright Β© 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», +# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; +# * compound commands having a testable exit status, especially Β«caseΒ»; +# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +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 + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/mysql/gradlew.bat b/examples/mysql/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/examples/mysql/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +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" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/mysql/grails-app/conf/application.yml b/examples/mysql/grails-app/conf/application.yml new file mode 100644 index 0000000..2ea6831 --- /dev/null +++ b/examples/mysql/grails-app/conf/application.yml @@ -0,0 +1,90 @@ +--- +info: + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' +spring: + freemarker: + checkTemplateLocation: false + groovy: + template: + check-template-location: false +--- +grails: + views: + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlet: html + taglib: none + staticparts: none + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + pdf: application/pdf + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + codegen: + defaultPackage: com.example.reveng.test + profile: web +--- +hibernate: + cache: + queries: false + use_query_cache: false + use_second_level_cache: false + format_sql: true + use_sql_comments: true +grails: + # do not reload since we'll be generating domain objects + run: + active: false + plugin: + reveng: + packageName: 'com.example.reveng.test' + versionColumns: + other: 'nonstandard_version_name' + manyToManyTables: + - user_role + manyToManyBelongsTos: + user_role: role + author_books: book +dataSource: + driverClassName: com.mysql.cj.jdbc.Driver + username: reveng + password: reveng + pooled: true + jmxExport: true + dbCreate: none + url: jdbc:mysql://localhost:3306/reveng diff --git a/examples/mysql/grails-app/conf/logback-spring.xml b/examples/mysql/grails-app/conf/logback-spring.xml new file mode 100644 index 0000000..9ffebba --- /dev/null +++ b/examples/mysql/grails-app/conf/logback-spring.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-app/grails-app/i18n/messages.properties b/examples/mysql/grails-app/i18n/messages.properties similarity index 100% rename from test-app/grails-app/i18n/messages.properties rename to examples/mysql/grails-app/i18n/messages.properties diff --git a/examples/mysql/grails-app/init/com/example/reveng/test/Application.groovy b/examples/mysql/grails-app/init/com/example/reveng/test/Application.groovy new file mode 100644 index 0000000..f5f1490 --- /dev/null +++ b/examples/mysql/grails-app/init/com/example/reveng/test/Application.groovy @@ -0,0 +1,23 @@ +package com.example.reveng.test + +import grails.boot.GrailsApp +import grails.boot.config.GrailsAutoConfiguration +import org.testcontainers.containers.MySQLContainer + +class Application extends GrailsAutoConfiguration { + + static void main(String[] args) { + try ( + MySQLContainer mysqlContainer = new MySQLContainer<>("mysql:9.1.0") + .withDatabaseName("reveng") + .withUsername("reveng") + .withPassword("reveng") + .withInitScript("reveng.sql") + ) { + mysqlContainer.start() + + System.setProperty('dataSource.url', mysqlContainer.getJdbcUrl()) + GrailsApp.run Application, args + } + } +} diff --git a/examples/mysql/grails-app/init/com/example/reveng/test/BootStrap.groovy b/examples/mysql/grails-app/init/com/example/reveng/test/BootStrap.groovy new file mode 100644 index 0000000..82c77cf --- /dev/null +++ b/examples/mysql/grails-app/init/com/example/reveng/test/BootStrap.groovy @@ -0,0 +1,21 @@ +package com.example.reveng.test + +import grails.core.GrailsApplication +import org.grails.plugins.reveng.ReverseEngineeringEngine +import org.grails.plugins.reveng.ReverseEngineeringFactory +import groovy.util.logging.Slf4j + +import java.nio.file.Path + +@Slf4j +class BootStrap { + GrailsApplication grailsApplication + + def init = { servletContext -> + ReverseEngineeringEngine engine = ReverseEngineeringFactory.create(Path.of('').toAbsolutePath().toFile(), grailsApplication) + log.info("Starting database reverse engineering, connecting to '${engine.properties.url}' as '${engine.properties.username}' ...") + engine.execute() + log.info('Finished database reverse engineering') + System.exit(0) + } +} \ No newline at end of file diff --git a/examples/mysql/grails-wrapper.jar b/examples/mysql/grails-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e2e73eb5973a1524e4b2d27ca6315f44473c494a GIT binary patch literal 5743 zcma)=bySpHyT*}Hx)G3&P+{n91SF)phXw%|a)uUkXlbMyq@){Z38`TuhK4~}7^Ea5 z1UYct?~RY|oU_jTtY<&_kL$Ykz1Mo~z1IF|s$gJ}qoLiqhjtz)W`uSf=r_Z4;9S3I z^13pdO6m&S7-*V*2@#Ih!tux;v1@_I^?PmqTc{?ZuB0HZqsyhHaHs|!QdQyP8Y58Q z1PsGR>a=+#_?OlX)Zp-Ok6|Sau14+W+{$n`+td(zh+_|6AtZ>;U->ZI$4!=>6-B%3`6RvE0%sI5^vA+k$K@!M4vWoozt2 z?p)R&OE5TEXI`C5i`=xBvp(@oy|xVI8q-YjkawBzM6g9POOl)lP15HzO~Rc`p5C;Q z<@*2<)t_1pj~YunJ4m4!2FH*l zR^B&1{a7g&wfh71va++%z8DjcFdS&WC(|mJpop5rLvUtkpN3KD)Tz{T;!YvmM>Tj#|tb{Ea>1(0g8&b=m658u^`R?kcPnIuf9U zj0e{5eSS%h9!-%fSf^Q|2U`bJ(I2R_)EnruGNw?mxFt9ddX;cIMYTPyuOy_DN*!Wg zNJv$#Tc4irS3IN=>p;l+-%Zfv>sw@!Q8}aTtuOC8WES`x^#kYL<)_i?jlrV(N2Vbo z#Crsw(8hU|@zlbGIW(YjjM>9jS;!f|nCX!21N+F3lZFZ>Z{_E0UPT%=8QXYbF?m;Q2QRu!pv$1z7_ zN4?#5@!NU_yA29*hNENr`vw%Fcts%sqDJ8fB|;c?A^q_Ml8WLo2px{6Uk2@Fypti^ zRC6Jo`Gh-6T&odzrYuGF_V?YQH7F;QqJ4;WQF&$a3Z!~#%S zX}yKd*i0dG&<|r!`kx$ZAmi!eGlmRoOf_)1b3>D!ZBFhBbg2KBrU>Z+)Mlh-Oz?Y* z*9TjRxzKs5=i$d>FT0jcPXVi}lfw61V=n@{H5C(&ffm6mqhNFFV+$m-m(dg}QIVXY-uLUwU!?8{M-AyqD)M5L?O`*=bn z7BwZ6Q5f_h^aZf7|3_*k;+!x17qwV4KuCqdprD@2H!MEJaZ0m0eJP`U76!9U*Q`8XMi5`Q46g0aO=p=st+#S#Z06GX65%e5k+qDeh>7g2zYXKyN!HE2X$ z?ucJS;bTpzP`!x};@m@k+gN5VpV3(=>Pq>c_5{;&A|;6;zJ}STj+{5|3I99)Dyho# zcgf#F8$)GKZ?gb%g=DXE!Ew41MqSR(NBrTC?3nM=4ypuW$o%&heh&IBivt#R0+hSo z=}?67&03lT?C}#0YWIJ#%~UZ(IrnM(Q0(gn5Q}f9^_-)xwA7Z%bi`s z^C(4ZU#3Mx^|4yvV(Zd=w)nb@2*OH?0zN{0v0ax9k>2fQr0A)A&IuREG`{%!rli*+ z?Lbji0!xEpe-xCyQ9>k)51<&9)D~u4Tmke+f1s=;E{wz|t?XbGvg=>x zr67AA$HBfjtaQo6oFOr#xNJ-+H@5xq#EErzjJ zZbp$M_5q(yDjm>S7c0D&Xr%X$T*AB;Z@*hMK6mppSRuAX+OvrpE|qNL^=Q;gs`${1 zJDGU0f!9f(WG}5Al9D5MDv!<{S+O-_gvBd`3``k#=*Py5jEqWwzQS|X`N7wQEV+l3 zH8qhUZsz`l)=xahvJZoV*}r^iRzu|KY#)HkTbcxz1x@1uMT=RBLTq26wneH~#x2eT zC(Vmc3kw7bh~ViG?{;`^sW)O^3-_y`_6YW4)zw6s<7EQxx`>ZU73hZzH8qA@3B1Q3 zeFo+?y)uSN)r?!+^LlL&Yo`2(5!1*nIOi=fI$rSK4`h>@iBj3F4F=q1p zd{OncArwZIM94J#@$bmDW$e-ht%*@ z@Z{hQ3ty3&FJfl|In0q~_e}H!a87w2A)M+oUR!p`Dpj;3MZwU28QiS-SminOTzT}a z(?DdSly>&8=TD5&f6ZKwFwPgAI^89`h?4x%M6fWCWU-Q%P_ z^YLNZ+PSN*q5gfNLQk0V*}DktA#lX1^801QM;(B&)oD!U)7P-?y$$;h03B~#a3(}r z<{njVCwUrndCin2BKeg#Goq`fl;&IZB`FsBiBheT-FRrYB3^|_zz+k08Q4$XM&zv6 z6&je%*ha#wBI9=EPeV8~KiLdcKGi?}d`4riAcid!|Fd3`j1>b1@f zuZtl2@hr&}LCfmOZ7%SBywP2928XRxr9P$6c=YE3u1I9(GMPJkR?{>-P)dg+2 zrx8mEs89sQ5X44ZiDA|u-DPev9A&1(3@$iw#f1rTUgkkB&GPsfg5>!p zIL^=8vrO}X;-T?RGUhhF6Iw?}b49Trd|kK%OEWMI#Ny^?+&D!)q~SDs3QJDO1&%ec z@Hyrdvcc>FO4D3~-%1+y+3NHiTF;*kQIBs@d7hj>mtdkBd;l>iN8(jF(lkF^=0!xJ zr?v)vf%m*|IwZfMRk86ax&d795zf8#(V-M|(hL2x1b52BZ^9~I=;N~RV|fCfrmFpr z(e+LXLi#az}$igJRZrI zAjP%XecrBUIloY6bDm%wppC`@wID;)fF_`gC>D9wCZwy8tc`|N5$@ncX@myLJC3^z z@hZAt2Yn#dUHs;*4p)6iK(2Nr6EBUxAyvGISvr0~+ACR(cEhEZKF?Youg?j)Bf=f2 z@%*dKWb1X{LivMH4L|%ISS=%5q~G?weq_7U7+VW@O{~}ZnUPm%BzqGQ(*Yazq1{!C zRh*SUTzSkBZ%?+*!!0uhY`Sb?n~j+n|mxM zQ&Gn!YcO_^LvBi$lm5~rRSH6WdbRl>F`ZKjslXh3h&(<{d3HvkSNgLkLC^~cv{KC{ zrE-G7noXs|W|jW(-+h21IY!o^TWi5XCt_#f*jK)6O7R*sZ^|AT$ikc?!eD+N61=nW zM&Xo5`B$ZcP(zM2ZcVAP2gyU3kTioc$0FW6>D3@|aNj3y8kK{+p^<%XA*Z`wfr5b+ zg7AVJ33M4I(Z(J?RwCr?=1S z-WtFvr;`dhmK>jPr6-VfITT1YmdtRJ)R{pDgnU1{is8RI%p?#pJZ(8>M7w{YwyeNM zj8QU;W$`WUP#08C;+%HE(h+<7$&DheatV)qS;=VZGG!G zFvY#JKTq@gk}~3rq4oFTnHJsf2IL9xu7^aLWIaZkp^Ox&|5=ha&B&_A=<3rkKT5U- zx|0Ee$hVD;uiiS?nBZ)O_7xq9VAbnS>MO3tDl&zh4{;Hs7ki3yVw6wuJy$&Y^9R`voAi2Hk#$eK88~rrXb6d>TbIY3im_b#u~hj@M>JLg6XLzB zd{+Hkx;>(krd5~8e%~$LpB1k`m8@OOnZL!8%B?;x-HRWH{EAz?uP@JT-9}vk#QV{-HjuP zl7<~M$}%q zi01k}_9qQ-b$9Xh`47&a26bRyzUCa%m}qD`x5YX>V2G`grn`%)tvkfQ7A)fdvA1=G zI9OXkT>fGnrY@v1|1o!_jh3m$sapCJq{-1joUdugq99apCZ1f3YjNs?#4XWi1IY@s0r7o1hi zvXeq#rQASUujT`mXImXRb6bqhkN_`80dKyHa2&Kt6Q$%V9^mw_{UqPmZ;jn;^xaqK z=eBrPIkI5MyvENNdHZZgdIHQK+oX)~UzAVcj;Y~H#g@aJ)T#Kr<8f5)Dk?U(y{M)% zrsaB!LX)13Ql4CTWg{8#XRk{${iIy>hC;3+DVr^pjA5Cyt(Bt4R4qS#spPru`Yw1YuiniOjIC(;1C$(qo>(DzW%sV7*$a-_L!3OPe+ zY@#^N4c8Y!V|)yucVYOEI^;V-FuFjYHh&uXN{7~!*?kL*0jG`)%|4K?=`N(HK1f9Q zvF_qeYN7PvkBV80461fpaBAFDg*3wVxU-Eb3)32B4-t7E+w-;~`aOxuFF(E({4i`G z;bzMCfY5^&60{vGQ?0xk%Xn&K(N&k%AgdMa$%pnX$+|jr`G=mdjwN3RfpN4s8t_@t zPfV>!MVqkNE~656%pI~?hUyE7L-B8-;2-=4-;|r^ERU%S2xl4ZtXlqf1fca+FHW)7 zK9i_D`o+)sPsf1#nNatECp<9|npH~%Xd8Zp|A z%+2>Np!iSQ{{$3o#5Yj@WxF2#vi$=u{xidE{&#~3|A?D~ldh-#f`=>{795jUo` z>*>Et|BVoDJ8vVzKfMLe&0Eg@4Jh8ucpI_&oss6ceg9d@KY+{aBsZo%3GU7D@JHNi tD4*o7B!3(hx2?Aii$Bc;(9zr0|Hsjxse*-lli}X=Lv>w|%nc?+`!Cq5GA;lB literal 0 HcmV?d00001 diff --git a/examples/mysql/grailsw b/examples/mysql/grailsw new file mode 100755 index 0000000..8d0cc12 --- /dev/null +++ b/examples/mysql/grailsw @@ -0,0 +1,152 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Grails start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRAILS_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-XX:+TieredCompilation" "-XX:TieredStopAtLevel=1" "-XX:CICompilerCount=3"' + + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +JAR_PATH=$APP_HOME/grails-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 + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + JAVACMD=`cygpath --unix "$JAVACMD"` + JAR_PATH=`cygpath --path --mixed "$JAR_PATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRAILS_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRAILS_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((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" ;; + esac +fi + +# Split up the JVM_OPTS And GRAILS_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRAILS_OPTS + +exec "$JAVACMD" -jar "${JVM_OPTS[@]}" "$JAR_PATH" "$@" diff --git a/examples/mysql/grailsw.bat b/examples/mysql/grailsw.bat new file mode 100644 index 0000000..14734e4 --- /dev/null +++ b/examples/mysql/grailsw.bat @@ -0,0 +1,89 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Grails startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRAILS_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-XX:+TieredCompilation" "-XX:TieredStopAtLevel=1" "-XX:CICompilerCount=3" + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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 + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_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=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line +set JAR_PATH=%APP_HOME%/grails-wrapper.jar + +@rem Execute Grails +"%JAVA_EXE%" -jar %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRAILS_OPTS% %JAR_PATH% %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRAILS_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRAILS_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/mysql/settings.gradle b/examples/mysql/settings.gradle new file mode 100644 index 0000000..0af0db8 --- /dev/null +++ b/examples/mysql/settings.gradle @@ -0,0 +1 @@ +includeBuild('../..') \ No newline at end of file diff --git a/test-app/reveng.sql b/examples/mysql/src/main/resources/reveng.sql similarity index 100% rename from test-app/reveng.sql rename to examples/mysql/src/main/resources/reveng.sql diff --git a/gradle.properties b/gradle.properties index e0dde34..f75f4a2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,10 @@ +projectVersion=5.0.0 grailsVersion=7.0.0-SNAPSHOT -vcsUrl=https://github.com/grails-plugins/grails-db-reverse-engineer + +# The bom will pull 6.x because it's inherited from spring, and we do not override it. +hibernateVersion=5.6.15.Final + +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M \ No newline at end of file diff --git a/gradle/common.gradle b/gradle/common.gradle deleted file mode 100644 index ca91568..0000000 --- a/gradle/common.gradle +++ /dev/null @@ -1,61 +0,0 @@ -def versionTxt = file('version.txt') -version = versionTxt.exists() ? versionTxt.text.trim() : '0.1' - -project.ext.setIfNotSet = { String name, value, boolean ext = true -> - if (!project.ext.has(name) || !project[name]) { - (ext ? project.ext : project)[name] = value - } -} - -setIfNotSet 'gradleWrapperVersion', '2.10' -setIfNotSet 'group', 'org.grails.plugins', false -setIfNotSet 'sourceCompatibility', 1.7 -setIfNotSet 'targetCompatibility', 1.7 - -apply plugin: 'idea' -apply plugin: 'war' -apply plugin: 'org.grails.grails-gsp' - -repositories { - mavenLocal() - mavenCentral() - maven { url 'https://repo.grails.org/grails/core' } -} - -dependencyManagement { - imports { - mavenBom "org.grails:grails-bom:$grailsVersion" - } - applyMavenExclusions false -} - -dependencies { - testCompile 'org.grails:grails-plugin-testing' -} - -// deletes everything from the build directory except for test reports -task cleanBuild { - if (!buildDir.exists()) return - - buildDir.eachFile { - if (it.file) { - it.delete() - } - else if (it.name != 'reports' && !it.name.startsWith('geb-reports') && !it.name.startsWith('test-results')) { - it.deleteDir() - } - } -} - -test { - testLogging { - exceptionFormat = 'full' - events 'failed', 'standardOut', 'standardError' - } - - beforeTest { descriptor -> logger.quiet " -- $descriptor" } -} - -task wrapper(type: Wrapper) { - gradleVersion = gradleWrapperVersion -} diff --git a/gradle/docs.gradle b/gradle/docs.gradle new file mode 100644 index 0000000..c4c0bb5 --- /dev/null +++ b/gradle/docs.gradle @@ -0,0 +1,43 @@ +tasks.register('cleanDocs', Delete) { + delete rootProject.layout.buildDirectory.dir('docs') +} + +tasks.register('aggregateGroovyApiDoc', Groovydoc) { + def groovyDocProjects = subprojects.findAll { it.name in ['grails-db-reverse-engineer'] } + dependsOn = [tasks.named('cleanDocs')] + groovyDocProjects.collect { it.tasks.named('groovydoc') } + + description = 'Generates Groovy API Documentation for all plugin projects under rootDir/gapi' + + group = 'documentation' + access = GroovydocAccess.PROTECTED + includeAuthor = false + includeMainForScripts = true + processScripts = true + source = groovyDocProjects.groovydoc.source + destinationDir = file("${rootProject.buildDir}/docs/gapi") + classpath = files(groovyDocProjects.groovydoc.classpath) + groovyClasspath = files(groovyDocProjects.groovydoc.groovyClasspath) +} + +tasks.register('docs') { + group = 'documentation' + dependsOn = ['aggregateGroovyApiDoc', 'docs:asciidoctor'] + finalizedBy 'copyAsciiDoctorDocs', 'ghPagesRootIndexPage' +} + +tasks.register('copyAsciiDoctorDocs', Copy) { + group = 'documentation' + dependsOn = ['docs'] + from "${rootProject.allprojects.find { it.name == 'docs'}.projectDir}/build" + includes = ['docs/**'] + into rootProject.buildDir + includeEmptyDirs = false +} + +tasks.register('ghPagesRootIndexPage', Copy) { + group = 'documentation' + dependsOn = ['docs'] + from file("${rootProject.allprojects.find { it.name == 'docs'}.projectDir}/src/docs/index.tmpl") + into rootProject.buildDir.toPath().resolve('docs').toFile() + rename 'index.tmpl', 'ghpages.html' +} \ No newline at end of file diff --git a/gradle/plugin.gradle b/gradle/plugin.gradle deleted file mode 100644 index 674cef8..0000000 --- a/gradle/plugin.gradle +++ /dev/null @@ -1,56 +0,0 @@ -apply from: 'gradle/common.gradle' - -apply plugin: 'maven-publish' -apply plugin: 'spring-boot' -apply plugin: 'org.grails.grails-plugin' -apply plugin: 'org.asciidoctor.convert' - -apply from: 'https://raw.githubusercontent.com/grails/grails-profile-repository/master/profiles/plugin/templates/grailsCentralPublishing.gradle' -apply from: 'https://raw.githubusercontent.com/grails/grails-profile-repository/master/profiles/plugin/templates/bintrayPublishing.gradle' - -setIfNotSet 'issueTrackerUrl', project.vcsUrl + '/issues' -setIfNotSet 'websiteUrl', project.vcsUrl - -dependencies { - provided 'javax.servlet:javax.servlet-api:3.1.0' - provided 'org.grails:grails-dependencies' - provided 'org.grails:grails-web-boot' - provided 'org.springframework.boot:spring-boot-starter-logging' -} - -asciidoctor { - separateOutputDirs = false - sourceDir = file('src/docs') - sources { - include 'index.adoc' - } - outputDir new File(buildDir, 'docs') - backends 'html5', 'pdf', 'epub3' - attributes 'source-highlighter': 'prettify', - icons: 'font', - setanchors: 'true', - idprefix: '', - idseparator: '-', - toc2: '', - numbered: '', - revnumber: project.version -} - -task docs(dependsOn: asciidoctor) << { - File dir = new File(buildDir, 'docs') - - ['pdf', 'epub'].each { String ext -> - File f = new File(dir, 'index.' + ext) - if (f.exists()) { - f.renameTo new File(dir, project.name + '-' + project.version + '.' + ext) - } - } - - new File(buildDir, 'docs/ghpages.html') << file('src/docs/index.tmpl').text.replaceAll('@VERSION@', project.version) - - copy { - from 'src/docs' - into new File(buildDir, 'docs').path - include '**/*.png' - } -} diff --git a/gradle/test-config.gradle b/gradle/test-config.gradle new file mode 100644 index 0000000..939812b --- /dev/null +++ b/gradle/test-config.gradle @@ -0,0 +1,22 @@ +apply plugin: 'com.adarshr.test-logger' +testlogger { + theme 'mocha' + showFullStackTraces true + showStandardStreams true + showPassedStandardStreams false + showSkippedStandardStreams false + showFailedStandardStreams true +} + +test.testLogging { + events "failed" + exceptionFormat "full" +} + +tasks.withType(Test).configureEach { testPlatform -> + useJUnitPlatform() + + // GitHub actions does not have the country set, only the language so force both here for tests to pass and be consistent + systemProperty 'user.country', 'US' + systemProperty 'user.language', 'en' +} \ No newline at end of file diff --git a/gradle/testapp.gradle b/gradle/testapp.gradle deleted file mode 100644 index 1ee2d30..0000000 --- a/gradle/testapp.gradle +++ /dev/null @@ -1,39 +0,0 @@ -File pluginDir = file('.').parentFile -File versionTxt -while (true) { - versionTxt = new File(pluginDir, 'version.txt') - if (versionTxt.exists()) { - break - } - pluginDir = pluginDir.parentFile -} - -apply plugin: 'org.grails.grails-web' - -apply from: new File(pluginDir, 'gradle/common.gradle').path - -project.ext.pluginVersion = versionTxt.text.trim() -project.ext.pluginName = pluginDir.name - 'grails-' - -dependencies { - compile "org.grails.plugins:$pluginName:$pluginVersion" - compile 'com.h2database:h2:1.4.190' - compile 'org.grails:grails-dependencies' - compile 'org.springframework.boot:spring-boot-autoconfigure' - compile 'org.springframework.boot:spring-boot-starter-logging' - compile 'org.springframework.boot:spring-boot-starter-tomcat' - console 'org.grails:grails-console' - runtime 'org.grails.plugins:asset-pipeline' - testCompile 'org.gebish:geb-core:0.12.2' - testCompile 'org.grails.plugins:geb' - - String seleniumVersion = '2.48.2' - // testCompile 'com.github.detro:phantomjsdriver:1.2.0' - testCompile 'com.codeborne:phantomjsdriver:1.2.1' // TODO switch back to com.github.detro:phantomjsdriver when this - // issue is resolved: https://github.com/detro/ghostdriver/issues/397 - - testCompile "org.seleniumhq.selenium:selenium-support:$seleniumVersion" - ['chrome', 'firefox'].each { String name -> - testCompile "org.seleniumhq.selenium:selenium-${name}-driver:$seleniumVersion" - } -} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..a4b76b9530d66f5e68d973ea569d8e19de379189 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e2847c8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..f5feea6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright Β© 2015-2021 the original 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions Β«$varΒ», Β«${var}Β», Β«${var:-default}Β», Β«${var+SET}Β», +# Β«${var#prefix}Β», Β«${var%suffix}Β», and Β«$( cmd )Β»; +# * compound commands having a testable exit status, especially Β«caseΒ»; +# * various built-in commands including Β«commandΒ», Β«setΒ», and Β«ulimitΒ». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +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 + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..9d21a21 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,94 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +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" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/grails-app/commands/grails/plugin/reveng/DbReverseEngineerCommand.groovy b/grails-app/commands/grails/plugin/reveng/DbReverseEngineerCommand.groovy deleted file mode 100644 index 12d1e1f..0000000 --- a/grails-app/commands/grails/plugin/reveng/DbReverseEngineerCommand.groovy +++ /dev/null @@ -1,110 +0,0 @@ -/* Copyright 2010-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 - * - * http://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. - */ -package grails.plugin.reveng - -import grails.core.GrailsApplication -import grails.dev.commands.ApplicationCommand -import grails.dev.commands.ExecutionContext - -/** - * @author Burt Beckwith - */ -class DbReverseEngineerCommand implements ApplicationCommand { - - final String description = 'Reverse-engineers a database and creates domain classes' - - boolean handle(ExecutionContext executionContext) { - RevengRunner runner = new RevengRunner() - def mergedConfig = buildMergedConfig(executionContext) - - println "Starting database reverse engineering, connecting to '$mergedConfig.url' as '$mergedConfig.username' ..." - - runner.run mergedConfig - - println 'Finished database reverse engineering' - - true - } - - protected Map buildMergedConfig(ExecutionContext ctx) { - - GrailsApplication grailsApplication = applicationContext.getBean(GrailsApplication) - def config = grailsApplication.config - - def mergedConfig = [ - alwaysMapManyToManyTables: false, - defaultCatalog: '', - defaultSchema: '', - excludeColumnAntPatterns: [:], - excludeColumnRegexes: [:], - excludeColumns: [:], - excludeTableAntPatterns: [], - excludeTableRegexes: [], - excludeTables: [], - includeTableAntPatterns: [], - includeTableRegexes: [], - includeTables: [], - manyToManyBelongsTos: [:], - manyToManyTables: [], - mappedManyToManyTables: [], - overwriteExisting: true, - versionColumns: [:] - ] - - def dsConfig = config.dataSource - - mergedConfig.driverClassName = dsConfig.driverClassName ?: 'org.h2.Driver' - mergedConfig.password = dsConfig.password ?: '' - mergedConfig.username = dsConfig.username ?: 'sa' - mergedConfig.url = dsConfig.url ?: 'jdbc:h2:mem:testDB' - if (dsConfig.dialect instanceof CharSequence) { - mergedConfig.dialect = dsConfig.dialect.toString() - } - else if (dsConfig.dialect instanceof Class) { - mergedConfig.dialect = dsConfig.dialect.name - } - - def revengConfig = config.grails.plugin.reveng - mergedConfig.packageName = revengConfig.packageName ?: - config.grails.codegen.defaultPackage ?: - grailsApplication.metadata.getApplicationName() - mergedConfig.destDir = new File(ctx.baseDir, revengConfig.destDir ?: 'grails-app/domain').canonicalPath - if (revengConfig.defaultSchema) { - mergedConfig.defaultSchema = revengConfig.defaultSchema - } - if (revengConfig.defaultCatalog) { - mergedConfig.defaultCatalog = revengConfig.defaultCatalog - } - if (revengConfig.overwriteExisting instanceof Boolean) { - mergedConfig.overwriteExisting = revengConfig.overwriteExisting - } - - if (revengConfig.alwaysMapManyToManyTables instanceof Boolean) { - mergedConfig.alwaysMapManyToManyTables = revengConfig.alwaysMapManyToManyTables - } - - for (String name in ['versionColumns', 'manyToManyTables', 'manyToManyBelongsTos', - 'includeTables', 'includeTableRegexes', 'includeTableAntPatterns', - 'excludeTables', 'excludeTableRegexes', 'excludeTableAntPatterns', - 'excludeColumns', 'excludeColumnRegexes', 'excludeColumnAntPatterns', - 'mappedManyToManyTables']) { - if (revengConfig[name]) { - mergedConfig[name] = revengConfig[name] - } - } - - mergedConfig - } -} diff --git a/grails-app/conf/BuildConfig.groovy b/grails-app/conf/BuildConfig.groovy deleted file mode 100644 index 093df34..0000000 --- a/grails-app/conf/BuildConfig.groovy +++ /dev/null @@ -1,28 +0,0 @@ -grails.project.work.dir = 'target' - -grails.project.dependency.resolver = 'maven' -grails.project.dependency.resolution = { - - inherits 'global' - log 'warn' - - repositories { - mavenLocal() - grailsCentral() - mavenCentral() - } - - dependencies { - compile 'org.hibernate:hibernate-tools:4.3.1.Final', { - excludes 'ant', 'common', 'org.eclipse.jdt.core', 'runtime', 'text' - } - - compile 'org.hibernate:hibernate-core:4.3.10.Final' - } - - plugins { - build ':release:3.1.2', ':rest-client-builder:2.1.1', { - export = false - } - } -} diff --git a/grails-app/conf/logback.groovy b/grails-app/conf/logback.groovy deleted file mode 100644 index cb42517..0000000 --- a/grails-app/conf/logback.groovy +++ /dev/null @@ -1,26 +0,0 @@ -import grails.util.BuildSettings -import grails.util.Environment - -String defaultPattern = '%-65(%.-2level %date{HH:mm:ss.SSS} %logger{32}) - %message%n' - -appender('STDOUT', ConsoleAppender) { - encoder(PatternLayoutEncoder) { - pattern = defaultPattern - } -} - -root ERROR, ['STDOUT'] - -File targetDir = BuildSettings.TARGET_DIR -if (Environment.developmentMode && targetDir) { - - appender('FULL_STACKTRACE', FileAppender) { - file = "$targetDir/stacktrace.log" - append = true - encoder(PatternLayoutEncoder) { - pattern = defaultPattern - } - } - - logger 'StackTrace', ERROR, ['FULL_STACKTRACE'], false -} diff --git a/plugin/build.gradle b/plugin/build.gradle new file mode 100644 index 0000000..0a76f83 --- /dev/null +++ b/plugin/build.gradle @@ -0,0 +1,41 @@ +buildscript { + repositories { + maven { url "https://repo.grails.org/grails/core/" } + } + dependencies { + classpath platform("org.grails:grails-bom:${grailsVersion}") + classpath "org.grails:grails-gradle-plugin" + } +} + +apply plugin: 'groovy' +apply plugin: 'java-library' +apply plugin: 'org.grails.grails-plugin' + +project.compileJava.options.release = 17 + +dependencies { + + implementation platform("org.grails:grails-bom:${grailsVersion}") + + compileOnly "jakarta.servlet:jakarta.servlet-api" + compileOnly 'org.grails:grails-dependencies' + compileOnly 'org.grails:grails-web-boot' + compileOnly 'org.springframework.boot:spring-boot-starter-logging' + compileOnly 'org.grails:grails-core' + compileOnly "org.hibernate:hibernate-core:${hibernateVersion}" + + api "org.hibernate:hibernate-tools:${hibernateVersion}", { +// exclude module: 'ant' +// exclude module: 'common' +// exclude module: 'freemarker' +// exclude module: 'org.eclipse.jdt.core' +// exclude module: 'runtime' +// exclude module: 'text' + } + + testImplementation("org.grails:grails-gorm-testing-support") + testImplementation("org.grails:grails-web-testing-support") +} + +apply from: rootProject.layout.projectDirectory.file('gradle/test-config.gradle') diff --git a/plugin/grails-app/commands/org/grails/plugins/reveng/DbReverseEngineerCommand.groovy b/plugin/grails-app/commands/org/grails/plugins/reveng/DbReverseEngineerCommand.groovy new file mode 100644 index 0000000..39267df --- /dev/null +++ b/plugin/grails-app/commands/org/grails/plugins/reveng/DbReverseEngineerCommand.groovy @@ -0,0 +1,42 @@ +/* Copyright 2010-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 + * + * http://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. + */ +package org.grails.plugins.reveng + +import grails.core.GrailsApplication +import grails.dev.commands.ApplicationCommand +import grails.dev.commands.ExecutionContext +import org.grails.plugins.reveng.ReverseEngineeringEngine +import org.grails.plugins.reveng.ReverseEngineeringFactory + +/** + * @author Burt Beckwith + */ +class DbReverseEngineerCommand implements ApplicationCommand { + + final String description = 'Reverse-engineers a database and creates domain classes' + + boolean handle(ExecutionContext executionContext) { + GrailsApplication grailsApplication = applicationContext.getBean(GrailsApplication) + ReverseEngineeringEngine engine = ReverseEngineeringFactory.create(executionContext.baseDir, grailsApplication) + + println "Starting database reverse engineering, connecting to '${engine.properties.url}' as '${engine.properties.username}' ..." + + engine.execute() + + println 'Finished database reverse engineering' + + true + } +} diff --git a/grails-app/conf/application.yml b/plugin/grails-app/conf/application.yml similarity index 100% rename from grails-app/conf/application.yml rename to plugin/grails-app/conf/application.yml diff --git a/plugin/grails-app/conf/logback-spring.xml b/plugin/grails-app/conf/logback-spring.xml new file mode 100644 index 0000000..18c5c03 --- /dev/null +++ b/plugin/grails-app/conf/logback-spring.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/groovy/grails/plugin/reveng/DbReverseEngineerGrailsPlugin.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/DbReverseEngineerGrailsPlugin.groovy similarity index 92% rename from src/main/groovy/grails/plugin/reveng/DbReverseEngineerGrailsPlugin.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/DbReverseEngineerGrailsPlugin.groovy index aa9e376..2f15fa9 100644 --- a/src/main/groovy/grails/plugin/reveng/DbReverseEngineerGrailsPlugin.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/DbReverseEngineerGrailsPlugin.groovy @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import grails.plugins.Plugin import groovy.transform.CompileStatic @@ -21,9 +21,9 @@ import groovy.util.logging.Slf4j @CompileStatic @Slf4j class DbReverseEngineerGrailsPlugin extends Plugin { - String grailsVersion = '3.0.0 > *' + String grailsVersion = '7.0.0-SNAPSHOT > *' String author = 'Burt Beckwith' - String authorEmail = 'burt@burtbeckwith.com' + String authorEmail = '' String title = 'Grails Database Reverse Engineering Plugin' String description = 'Reverse-engineers a database to Grails domain classes.' String documentation = 'http://grails-plugins.github.io/grails-db-reverse-engineer/' diff --git a/src/main/groovy/grails/plugin/reveng/GrailsCfg2JavaTool.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsCfg2JavaTool.groovy similarity index 78% rename from src/main/groovy/grails/plugin/reveng/GrailsCfg2JavaTool.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/GrailsCfg2JavaTool.groovy index 41d9f72..f172f2c 100644 --- a/src/main/groovy/grails/plugin/reveng/GrailsCfg2JavaTool.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsCfg2JavaTool.groovy @@ -12,12 +12,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import groovy.transform.CompileStatic import groovy.util.logging.Slf4j import org.hibernate.cfg.Configuration import org.hibernate.mapping.PersistentClass +import org.hibernate.tool.api.metadata.MetadataDescriptor import org.hibernate.tool.hbm2x.Cfg2HbmTool import org.hibernate.tool.hbm2x.Cfg2JavaTool import org.hibernate.tool.hbm2x.pojo.POJOClass @@ -32,17 +33,17 @@ import org.hibernate.tool.hbm2x.pojo.POJOClass class GrailsCfg2JavaTool extends Cfg2JavaTool { protected Cfg2HbmTool c2h - protected Configuration configuration + protected MetadataDescriptor metadataDescriptor protected Map revengConfig - GrailsCfg2JavaTool(Cfg2HbmTool c2h, Configuration configuration, Map revengConfig) { + GrailsCfg2JavaTool(Cfg2HbmTool c2h, MetadataDescriptor metadataDescriptor, Map revengConfig) { this.c2h = c2h - this.configuration = configuration + this.metadataDescriptor = metadataDescriptor this.revengConfig = revengConfig } @Override POJOClass getPOJOClass(PersistentClass comp) { - new GrailsEntityPOJOClass(comp, this, c2h, configuration, revengConfig) + new GrailsEntityPOJOClass(comp, this, c2h, metadataDescriptor, revengConfig) } } diff --git a/src/main/groovy/grails/plugin/reveng/GrailsEntityPOJOClass.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsEntityPOJOClass.groovy similarity index 97% rename from src/main/groovy/grails/plugin/reveng/GrailsEntityPOJOClass.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/GrailsEntityPOJOClass.groovy index 8f7de1f..787cc00 100644 --- a/src/main/groovy/grails/plugin/reveng/GrailsEntityPOJOClass.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsEntityPOJOClass.groovy @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import groovy.util.logging.Slf4j import org.hibernate.cfg.Configuration @@ -22,9 +22,11 @@ import org.hibernate.mapping.ManyToOne import org.hibernate.mapping.PersistentClass import org.hibernate.mapping.Property import org.hibernate.mapping.UniqueKey +import org.hibernate.tool.api.metadata.MetadataDescriptor import org.hibernate.tool.hbm2x.Cfg2HbmTool import org.hibernate.tool.hbm2x.Cfg2JavaTool import org.hibernate.tool.hbm2x.pojo.EntityPOJOClass +import org.hibernate.tool.internal.metadata.JdbcMetadataDescriptor import org.hibernate.type.CalendarDateType import org.hibernate.type.CalendarType import org.hibernate.type.DateType @@ -53,17 +55,15 @@ class GrailsEntityPOJOClass extends EntityPOJOClass { protected PersistentClass clazz protected Cfg2HbmTool c2h - protected Configuration configuration protected Map revengConfig protected String newline = System.getProperty('line.separator') protected List newProperties = [] GrailsEntityPOJOClass(PersistentClass clazz, Cfg2JavaTool cfg, Cfg2HbmTool c2h, - Configuration configuration, Map revengConfig) { + MetadataDescriptor metadataDescriptor, Map revengConfig) { super(clazz, cfg) this.clazz = clazz this.c2h = c2h - this.configuration = configuration this.revengConfig = revengConfig } @@ -190,9 +190,9 @@ class GrailsEntityPOJOClass extends EntityPOJOClass { } if (needsEqualsHashCode()) { - fixed << delimiter << 'import org.apache.commons.lang.builder.EqualsBuilder' + fixed << delimiter << 'import org.apache.commons.lang3.builder.EqualsBuilder' delimiter = newline - fixed << delimiter << 'import org.apache.commons.lang.builder.HashCodeBuilder' << delimiter + fixed << delimiter << 'import org.apache.commons.lang3.builder.HashCodeBuilder' << delimiter } imports = fixed diff --git a/src/main/groovy/grails/plugin/reveng/GrailsJdbcBinder.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsJdbcBinder.groovy similarity index 56% rename from src/main/groovy/grails/plugin/reveng/GrailsJdbcBinder.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/GrailsJdbcBinder.groovy index 3562587..184de36 100644 --- a/src/main/groovy/grails/plugin/reveng/GrailsJdbcBinder.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsJdbcBinder.groovy @@ -12,19 +12,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import groovy.transform.CompileStatic import groovy.util.logging.Slf4j +import org.hibernate.boot.spi.InFlightMetadataCollector +import org.hibernate.boot.spi.MetadataBuildingContext +import org.hibernate.cfg.AvailableSettings import org.hibernate.cfg.JDBCBinder -import org.hibernate.cfg.JDBCMetaDataConfiguration import org.hibernate.cfg.JDBCReaderFactory -import org.hibernate.cfg.Mappings -import org.hibernate.cfg.Settings import org.hibernate.cfg.reveng.DatabaseCollector import org.hibernate.cfg.reveng.JDBCReader import org.hibernate.cfg.reveng.MappingsDatabaseCollector import org.hibernate.cfg.reveng.ReverseEngineeringStrategy +import org.hibernate.service.ServiceRegistry /** * Registers a ProgressListener to log status messages. @@ -35,27 +36,27 @@ import org.hibernate.cfg.reveng.ReverseEngineeringStrategy @Slf4j class GrailsJdbcBinder extends JDBCBinder { - protected Settings settings - protected JDBCMetaDataConfiguration cfg - protected Mappings mappings + protected ServiceRegistry serviceRegistry + protected Properties properties + protected InFlightMetadataCollector metadataCollector protected ReverseEngineeringStrategy revengStrategy - GrailsJdbcBinder(JDBCMetaDataConfiguration cfg, Settings settings, Mappings mappings, - ReverseEngineeringStrategy revengStrategy) { - super(cfg, settings, mappings, revengStrategy) - this.settings = settings - this.cfg = cfg - this.mappings = mappings + GrailsJdbcBinder(ServiceRegistry serviceRegistry, Properties properties, MetadataBuildingContext mdbc, + ReverseEngineeringStrategy revengStrategy, boolean preferBasicCompositeIds) { + super(serviceRegistry, properties, mdbc, revengStrategy, preferBasicCompositeIds) + this.serviceRegistry = serviceRegistry + this.properties = properties + this.metadataCollector = mdbc.metadataCollector this.revengStrategy = revengStrategy } @Override DatabaseCollector readDatabaseSchema(String catalog, String schema) { - catalog = catalog ?: settings.defaultCatalogName - schema = schema ?: settings.defaultSchemaName + catalog = catalog ?: properties.getProperty(AvailableSettings.DEFAULT_CATALOG) + schema = schema ?: properties.getProperty(AvailableSettings.DEFAULT_SCHEMA) - JDBCReader reader = JDBCReaderFactory.newJDBCReader(cfg.properties, settings,revengStrategy, cfg.serviceRegistry) - DatabaseCollector dbs = new MappingsDatabaseCollector(mappings, reader.metaDataDialect) + JDBCReader reader = JDBCReaderFactory.newJDBCReader(properties, revengStrategy, serviceRegistry) + DatabaseCollector dbs = new MappingsDatabaseCollector(metadataCollector, reader.metaDataDialect) reader.readDatabaseSchema dbs, catalog, schema, new ReverseEngineerProgressListener() dbs } diff --git a/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsJdbcMetadataDescriptor.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsJdbcMetadataDescriptor.groovy new file mode 100644 index 0000000..0fa66f3 --- /dev/null +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsJdbcMetadataDescriptor.groovy @@ -0,0 +1,118 @@ +/* Copyright 2010-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 + * + * http://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. + */ +package org.grails.plugins.reveng + +import org.hibernate.MappingException +import org.hibernate.boot.Metadata +import org.hibernate.boot.internal.BootstrapContextImpl +import org.hibernate.boot.internal.InFlightMetadataCollectorImpl +import org.hibernate.boot.internal.MetadataBuilderImpl +import org.hibernate.boot.internal.MetadataBuildingContextRootImpl +import org.hibernate.boot.internal.MetadataImpl +import org.hibernate.boot.registry.StandardServiceRegistry +import org.hibernate.boot.registry.StandardServiceRegistryBuilder +import org.hibernate.boot.spi.MetadataBuildingContext +import org.hibernate.cfg.reveng.ReverseEngineeringStrategy +import org.hibernate.engine.spi.Mapping +import org.hibernate.id.factory.IdentifierGeneratorFactory +import org.hibernate.mapping.PersistentClass +import org.hibernate.mapping.Property +import org.hibernate.tool.internal.metadata.JdbcMetadataDescriptor +import org.hibernate.type.Type + +/** + * Creates a GrailsJdbcBinder to register a logging ProgressListener. + * + * @author Burt Beckwith + */ +class GrailsJdbcMetadataDescriptor extends JdbcMetadataDescriptor { + + protected ReverseEngineeringStrategy reverseEngineeringStrategy + protected boolean preferBasicCompositeIds + + GrailsJdbcMetadataDescriptor(ReverseEngineeringStrategy reverseEngineeringStrategy, Properties properties, boolean preferBasicCompositeIds) { + super(reverseEngineeringStrategy, properties, preferBasicCompositeIds) + this.reverseEngineeringStrategy = reverseEngineeringStrategy + this.preferBasicCompositeIds = preferBasicCompositeIds + } + + @Override + Metadata createMetadata() { + StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder() + .applySettings(getProperties()) + .build() + MetadataBuilderImpl.MetadataBuildingOptionsImpl metadataBuildingOptions = + new MetadataBuilderImpl.MetadataBuildingOptionsImpl(serviceRegistry) + BootstrapContextImpl bootstrapContext = new BootstrapContextImpl( + serviceRegistry, + metadataBuildingOptions) + metadataBuildingOptions.setBootstrapContext(bootstrapContext) + InFlightMetadataCollectorImpl metadataCollector = new InFlightMetadataCollectorImpl(bootstrapContext, metadataBuildingOptions) + MetadataBuildingContext metadataBuildingContext = + new MetadataBuildingContextRootImpl( + bootstrapContext, + metadataBuildingOptions, + metadataCollector) + MetadataImpl metadata = metadataCollector + .buildMetadataInstance(metadataBuildingContext) + metadata.getTypeConfiguration().scope(metadataBuildingContext) + GrailsJdbcBinder binder = new GrailsJdbcBinder( + serviceRegistry, + getProperties(), + metadataBuildingContext, + reverseEngineeringStrategy, + preferBasicCompositeIds) + + binder.readFromDatabase( + null, + null, + buildMapping(metadata)) + return metadata + } + + // TODO: This method will become org.hibernate.tool.internal.reveng.BinderMapping in later hibernate versions + private Mapping buildMapping(final Metadata metadata) { + return new Mapping() { + /** + * Returns the identifier type of a mapped class + */ + Type getIdentifierType(String persistentClass) throws MappingException { + final PersistentClass pc = metadata.getEntityBinding(persistentClass) + if (pc==null) throw new MappingException("persistent class not known: " + persistentClass) + return pc.getIdentifier().getType() + } + + String getIdentifierPropertyName(String persistentClass) throws MappingException { + final PersistentClass pc = metadata.getEntityBinding(persistentClass) + if (pc==null) throw new MappingException("persistent class not known: " + persistentClass) + if ( !pc.hasIdentifierProperty() ) return null + return pc.getIdentifierProperty().getName() + } + + Type getReferencedPropertyType(String persistentClass, String propertyName) throws MappingException + { + final PersistentClass pc = metadata.getEntityBinding(persistentClass) + if (pc==null) throw new MappingException("persistent class not known: " + persistentClass) + Property prop = pc.getProperty(propertyName) + if (prop==null) throw new MappingException("property not known: " + persistentClass + '.' + propertyName) + return prop.getType() + } + + IdentifierGeneratorFactory getIdentifierGeneratorFactory() { + return null + } + } + } +} diff --git a/src/main/groovy/grails/plugin/reveng/GrailsPojoExporter.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsPojoExporter.groovy similarity index 95% rename from src/main/groovy/grails/plugin/reveng/GrailsPojoExporter.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/GrailsPojoExporter.groovy index baa573f..2da30b7 100644 --- a/src/main/groovy/grails/plugin/reveng/GrailsPojoExporter.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsPojoExporter.groovy @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import freemarker.cache.TemplateLoader import org.hibernate.tool.hbm2x.Cfg2HbmTool @@ -62,7 +62,7 @@ ${pojo.generateImports()}${classbody}''' GrailsPojoExporter(boolean overwrite, Map revengConfig) { this.overwrite = overwrite - cfg2JavaTool = new GrailsCfg2JavaTool(cfg2HbmTool, configuration, revengConfig) + cfg2JavaTool = new GrailsCfg2JavaTool(cfg2HbmTool, metadataDescriptor, revengConfig) } @Override diff --git a/src/main/groovy/grails/plugin/reveng/GrailsReverseEngineeringStrategy.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsReverseEngineeringStrategy.groovy similarity index 99% rename from src/main/groovy/grails/plugin/reveng/GrailsReverseEngineeringStrategy.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/GrailsReverseEngineeringStrategy.groovy index 4ad4278..078f156 100644 --- a/src/main/groovy/grails/plugin/reveng/GrailsReverseEngineeringStrategy.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsReverseEngineeringStrategy.groovy @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import groovy.transform.CompileStatic import groovy.util.logging.Slf4j diff --git a/src/main/groovy/grails/plugin/reveng/GrailsTemplateProducer.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsTemplateProducer.groovy similarity index 99% rename from src/main/groovy/grails/plugin/reveng/GrailsTemplateProducer.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/GrailsTemplateProducer.groovy index d56178d..dea5b99 100644 --- a/src/main/groovy/grails/plugin/reveng/GrailsTemplateProducer.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/GrailsTemplateProducer.groovy @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import groovy.transform.CompileStatic import groovy.util.logging.Slf4j diff --git a/src/main/groovy/grails/plugin/reveng/ReverseEngineerProgressListener.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineerProgressListener.groovy similarity index 96% rename from src/main/groovy/grails/plugin/reveng/ReverseEngineerProgressListener.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineerProgressListener.groovy index 33db5e1..89f8190 100644 --- a/src/main/groovy/grails/plugin/reveng/ReverseEngineerProgressListener.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineerProgressListener.groovy @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import groovy.transform.CompileStatic import groovy.util.logging.Slf4j diff --git a/src/main/groovy/grails/plugin/reveng/Reenigne.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringEngine.groovy similarity index 81% rename from src/main/groovy/grails/plugin/reveng/Reenigne.groovy rename to plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringEngine.groovy index 46bf64d..c787fee 100644 --- a/src/main/groovy/grails/plugin/reveng/Reenigne.groovy +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringEngine.groovy @@ -12,13 +12,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package grails.plugin.reveng +package org.grails.plugins.reveng import grails.util.GrailsUtil import groovy.transform.CompileStatic +import groovy.transform.MapConstructor import groovy.util.logging.Slf4j +import org.hibernate.boot.Metadata import org.hibernate.cfg.Environment import org.hibernate.cfg.reveng.ReverseEngineeringSettings +import org.hibernate.tool.hbm2x.ArtifactCollector import org.hibernate.tool.hbm2x.Exporter import org.hibernate.tool.hbm2x.HibernateMappingExporter @@ -29,7 +32,7 @@ import org.hibernate.tool.hbm2x.HibernateMappingExporter */ @CompileStatic @Slf4j -class Reenigne { +class ReverseEngineeringEngine { File destDir String packageName @@ -53,14 +56,14 @@ class Reenigne { GrailsReverseEngineeringStrategy reverseEngineeringStrategy = GrailsReverseEngineeringStrategy.INSTANCE - protected GrailsPojoExporter pojoExporter protected HibernateMappingExporter hbmXmlExporter = new HibernateMappingExporter() - protected GrailsJdbcMetaDataConfiguration configuration = new GrailsJdbcMetaDataConfiguration() - protected Properties properties = new Properties() + protected GrailsPojoExporter pojoExporter + protected GrailsJdbcMetadataDescriptor metadataDescriptor void execute() { try { buildConfiguration() + Metadata metadata = metadataDescriptor.createMetadata() pojoExporter = new GrailsPojoExporter(overwrite, revengConfig) configureExporter pojoExporter @@ -73,21 +76,21 @@ class Reenigne { // hbmXmlExporter.start() } catch (e) { - GrailsUtil.sanitize e + GrailsUtil.deepSanitize(e) e.printStackTrace() throw e } } protected void configureExporter(Exporter exporter) { - exporter.properties = properties - exporter.configuration = configuration + exporter.properties.putAll(metadataDescriptor.properties) + exporter.artifactCollector = new ArtifactCollector() + exporter.metadataDescriptor = metadataDescriptor exporter.outputDirectory = destDir } protected void buildConfiguration() { - properties.putAll configuration.properties - + Properties properties = new Properties() properties[Environment.DRIVER] = driverClass properties[Environment.PASS] = password properties[Environment.URL] = url @@ -102,17 +105,12 @@ class Reenigne { properties[Environment.DEFAULT_CATALOG] = defaultCatalog } - configuration.properties = properties - - configuration.preferBasicCompositeIds = preferBasicCompositeIds - reverseEngineeringStrategy.settings = new ReverseEngineeringSettings(reverseEngineeringStrategy) .setDefaultPackageName(packageName) .setDetectManyToMany(detectManyToMany) .setDetectOneToOne(detectOneToOne) .setDetectOptimisticLock(detectOptimisticLock) - configuration.reverseEngineeringStrategy = reverseEngineeringStrategy - configuration.readFromJDBC defaultCatalog, defaultSchema + metadataDescriptor = new GrailsJdbcMetadataDescriptor(reverseEngineeringStrategy, properties, preferBasicCompositeIds) } } diff --git a/plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringFactory.groovy b/plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringFactory.groovy new file mode 100644 index 0000000..c4c5c4b --- /dev/null +++ b/plugin/src/main/groovy/org/grails/plugins/reveng/ReverseEngineeringFactory.groovy @@ -0,0 +1,182 @@ +/* Copyright 2012-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 + * + * http://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. + */ +package org.grails.plugins.reveng + +import grails.core.GrailsApplication +import groovy.transform.CompileDynamic +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import groovy.util.logging.Slf4j + +/** + * @author Burt Beckwith + */ +@CompileStatic +@Slf4j +class ReverseEngineeringFactory { + static ReverseEngineeringEngine create(File baseDir, GrailsApplication application) { + Map config = buildMergedConfig(baseDir, application) + + ReverseEngineeringEngine engine = init(config) + engine + } + + @CompileDynamic + protected static Map buildMergedConfig(File baseDir, GrailsApplication application) { + def config = application.config + + def mergedConfig = [ + alwaysMapManyToManyTables: false, + defaultCatalog: '', + defaultSchema: '', + excludeColumnAntPatterns: [:], + excludeColumnRegexes: [:], + excludeColumns: [:], + excludeTableAntPatterns: [], + excludeTableRegexes: [], + excludeTables: [], + includeTableAntPatterns: [], + includeTableRegexes: [], + includeTables: [], + manyToManyBelongsTos: [:], + manyToManyTables: [], + mappedManyToManyTables: [], + overwriteExisting: true, + versionColumns: [:] + ] + + def dsConfig = config.dataSource + + mergedConfig.driverClassName = dsConfig.driverClassName ?: 'org.h2.Driver' + mergedConfig.password = dsConfig.password ?: '' + mergedConfig.username = dsConfig.username ?: 'sa' + mergedConfig.url = dsConfig.url ?: 'jdbc:h2:mem:testDB' + if (dsConfig.dialect instanceof CharSequence) { + mergedConfig.dialect = dsConfig.dialect.toString() + } + else if (dsConfig.dialect instanceof Class) { + mergedConfig.dialect = dsConfig.dialect.name + } + + def pluginConfiguration = config.org.grails.plugins.reveng + mergedConfig.packageName = pluginConfiguration.packageName ?: config.grails.codegen.defaultPackage ?: application.metadata.getApplicationName() + mergedConfig.destDir = new File(baseDir, pluginConfiguration.destDir ?: 'grails-app/domain').canonicalPath + if (pluginConfiguration.defaultSchema) { + mergedConfig.defaultSchema = pluginConfiguration.defaultSchema + } + if (pluginConfiguration.defaultCatalog) { + mergedConfig.defaultCatalog = pluginConfiguration.defaultCatalog + } + if (pluginConfiguration.overwriteExisting instanceof Boolean) { + mergedConfig.overwriteExisting = pluginConfiguration.overwriteExisting + } + + if (pluginConfiguration.alwaysMapManyToManyTables instanceof Boolean) { + mergedConfig.alwaysMapManyToManyTables = pluginConfiguration.alwaysMapManyToManyTables + } + + for (String name in ['versionColumns', 'manyToManyTables', 'manyToManyBelongsTos', + 'includeTables', 'includeTableRegexes', 'includeTableAntPatterns', + 'excludeTables', 'excludeTableRegexes', 'excludeTableAntPatterns', + 'excludeColumns', 'excludeColumnRegexes', 'excludeColumnAntPatterns', + 'mappedManyToManyTables']) { + if (pluginConfiguration[name]) { + mergedConfig[name] = pluginConfiguration[name] + } + } + + mergedConfig + } + + /** + * Note: defaults are set in the DbReverseEngineerCommand instead of here + */ + @PackageScope + static ReverseEngineeringEngine init(Map config) { + ReverseEngineeringEngine engine = new ReverseEngineeringEngine( + revengConfig: config, + driverClass: config.driverClassName as String, + password: config.password as String, + username: config.username as String, + url: config.url as String, + dialect: config.dialect as String, + packageName: config.packageName as String, + destDir: new File(config.destDir as String), + overwrite: config.overwriteExisting as boolean) + + if (config.defaultSchema) { + engine.defaultSchema = config.defaultSchema + } + if (config.defaultCatalog) { + engine.defaultCatalog = config.defaultCatalog + } + + def strategy = engine.reverseEngineeringStrategy + + ((Map)config.versionColumns).each { String table, String column -> + strategy.addVersionColumn table, column + } + + ((Collection)config.manyToManyTables).each { String table -> + strategy.addManyToManyTable table + } + + ((Map)config.manyToManyBelongsTos).each { String manyTable, String belongsTable -> + strategy.setManyToManyBelongsTo manyTable, belongsTable + } + + ((Collection)config.includeTables).each { String table -> + strategy.addIncludeTable table + } + + ((Collection)config.includeTableRegexes).each { String pattern -> + strategy.addIncludeTableRegex pattern + } + + ((Collection)config.includeTableAntPatterns).each { String pattern -> + strategy.addIncludeTableAntPattern pattern + } + + ((Collection)config.excludeTables).each { String table -> + strategy.addExcludeTable table + } + + ((Collection)config.excludeTableRegexes).each { String pattern -> + strategy.addExcludeTableRegex pattern + } + + ((Collection)config.excludeTableAntPatterns).each { String pattern -> + strategy.addExcludeTableAntPattern pattern + } + + ((Map>)config.excludeColumns).each { String table, List columns -> + strategy.addExcludeColumns table, columns + } + + ((Map>)config.excludeColumnRegexes).each { String table, List patterns -> + strategy.addExcludeColumnRegexes table, patterns + } + + ((Map>)config.excludeColumnAntPatterns).each { String table, List patterns -> + strategy.addExcludeColumnAntPatterns table, patterns + } + + ((Collection)config.mappedManyToManyTables).each { String table -> strategy.addMappedManyToManyTable table } + + strategy.alwaysMapManyToManyTables = config.alwaysMapManyToManyTables as boolean + + engine + } +} diff --git a/settings.gradle b/settings.gradle index 7b4bebb..290ba46 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,8 @@ -rootProject.name = 'db-reverse-engineer' +rootProject.name = 'grails-db-reverse-engineer-root' + +include 'plugin' +findProject(':plugin').name = 'grails-db-reverse-engineer' + +include 'docs' + +// examples must be loaded as separate projects due to the way grails commands need resolvable on the gradle classpath diff --git a/src/docs/configuration.adoc b/src/docs/configuration.adoc deleted file mode 100644 index 8cfadc5..0000000 --- a/src/docs/configuration.adoc +++ /dev/null @@ -1,58 +0,0 @@ -[[configuration]] -== Configuration - -There are several configuration options for the plugin. - -=== Core properties - -These configuration options are the ones you're most likely need to set. They include the package name of the generated files (`grails.plugin.reveng.packageName`), and information about which side of a many-to-many is the "`belongsTo`" side (`grails.plugin.reveng.manyToManyBelongsTos`). - -[width="100%",options="header"] -|==================== -| *Property* | *Default* | *Meaning* -| grails.plugin.reveng.packageName | application name | package name for the generated domain classes -| grails.plugin.reveng.manyToManyBelongsTos | none | a Map of join table name -> belongsTo table name to specify which of the two domain classes is the 'belongsTo' side of the relationship -|==================== - -=== Inclusion/exclusion properties - -These configuration options let you define which tables and columns to include in processing. By default all tables and columns are processed. - -If you specify tables to include (`g.p.r.includeTables`, `g.p.r.includeTableRegexes`, and/or `g.p.r.includeTableAntPatterns`) then only those will be used. If you specify any of these options then all of the table exclusion options are ignored; this is useful when you have already run the reverse-engineer command but want to re-run it for only a subset of tables. - -If you specify tables to exclude (`g.p.r.excludeTables`, `g.p.r.excludeTableRegexes`, and/or `g.p.r.excludeTableAntPatterns`) then any matching tables will be ignored. - -You can also specify columns to exclude (`g.p.r.excludeColumns`, `g.p.r.excludeColumnRegexes`, and/or `g.p.r.excludeColumnAntPatterns`) and any matching columns will be ignored. These options can be used with table include rules or table exclude rules - any tables that are included or not excluded will have their columns included or excludede based on these rules. - -One addition property, `grails.plugin.reveng.manyToManyTables`, doesn't affect whether a table is processed, but rather determines whether a table that won't look like a many-to-many table to the Hibernate Tools library (because it has more than two columns) is considered a many-to-many table. - -[width="100%",options="header"] -|==================== -| *Property* | *Default* | *Meaning* -| grails.plugin.reveng.includeTables | none | a List of table names to include for processing -| grails.plugin.reveng.includeTableRegexes | none | a List of table name regex patterns to include for processing -| grails.plugin.reveng.includeTableAntPatterns | none | a List of table name Ant-style patterns to include for processing -| grails.plugin.reveng.excludeTables | none | a List of table names to exclude from processing -| grails.plugin.reveng.excludeTableRegexes | none | a List of table name regex patterns to exclude from processing -| grails.plugin.reveng.excludeTableAntPatterns | none | a List of table name Ant-style patterns to exclude from processing -| grails.plugin.reveng.excludeColumns | none | a Map of table name -> List of column names to ignore -| grails.plugin.reveng.excludeColumnRegexes | none | a Map of table name -> List of column name regex patterns to ignore -| grails.plugin.reveng.excludeColumnAntPatterns | none | a Map of table name -> List of column name Ant-style patterns to ignore -| grails.plugin.reveng.manyToManyTables | none | a List of table names that should be considered many-to-many join tables; needed for join tables that have more columns than the two foreign keys -|==================== - -=== Other properties - -These remaining configuration options allow you to specify the folder where the domain classes are generated (`g.p.r.destDir`), whether to overwrite existing classes (`g.p.r.overwriteExisting`) and non-standard 'version' column names (`g.p.r.versionColumns`). - -There are also properties to set the default schema name (`g.p.r.defaultSchema`) and catalog (`g.p.r.defaultCatalog`) name which are useful when working with Oracle. - -[width="100%",options="header"] -|==================== -| *Property* | *Default* | *Meaning* -| grails.plugin.reveng.destDir | 'grails-app/domain' | destination folder for the generated classes, relative to the project root -| grails.plugin.reveng.versionColumns | none | a Map of table name -> version column name, for tables with an optimistic locking column that's not named 'version' -| grails.plugin.reveng.overwriteExisting | `true` | whether to overwrite existing domain classes -| grails.plugin.reveng.defaultSchema | none | the default database schema name -| grails.plugin.reveng.defaultCatalog | none | the default database catalog name -|==================== diff --git a/src/main/groovy/grails/plugin/reveng/GrailsJdbcMetaDataConfiguration.groovy b/src/main/groovy/grails/plugin/reveng/GrailsJdbcMetaDataConfiguration.groovy deleted file mode 100644 index a42bf7e..0000000 --- a/src/main/groovy/grails/plugin/reveng/GrailsJdbcMetaDataConfiguration.groovy +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright 2010-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 - * - * http://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. - */ -package grails.plugin.reveng - -import org.hibernate.cfg.JDBCMetaDataConfiguration - -/** - * Creates a GrailsJdbcBinder to register a logging ProgressListener. - * - * @author Burt Beckwith - */ -class GrailsJdbcMetaDataConfiguration extends JDBCMetaDataConfiguration { - - void readFromJDBC(String defaultCatalog, String defaultSchema) { - - GrailsJdbcBinder binder = new GrailsJdbcBinder( - this, buildSettings(), createMappings(), reverseEngineeringStrategy) - - binder.readFromDatabase defaultCatalog, defaultSchema, buildMapping(this) - } -} diff --git a/src/main/groovy/grails/plugin/reveng/RevengRunner.groovy b/src/main/groovy/grails/plugin/reveng/RevengRunner.groovy deleted file mode 100644 index f027184..0000000 --- a/src/main/groovy/grails/plugin/reveng/RevengRunner.groovy +++ /dev/null @@ -1,103 +0,0 @@ -/* Copyright 2012-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 - * - * http://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. - */ -package grails.plugin.reveng - -import groovy.transform.CompileStatic -import groovy.util.logging.Slf4j - -/** - * @author Burt Beckwith - */ -@CompileStatic -@Slf4j -class RevengRunner { - - void run(Map config) { - - Reenigne reenigne = new Reenigne( - revengConfig: config, - driverClass: config.driverClassName as String, - password: config.password as String, - username: config.username as String, - url: config.url as String, - dialect: config.dialect as String, - packageName: config.packageName as String, - destDir: new File(config.destDir as String), - overwrite: config.overwriteExisting as boolean) - - if (config.defaultSchema) { - reenigne.defaultSchema = config.defaultSchema - } - if (config.defaultCatalog) { - reenigne.defaultCatalog = config.defaultCatalog - } - - def strategy = reenigne.reverseEngineeringStrategy - - ((Map)config.versionColumns).each { String table, String column -> - strategy.addVersionColumn table, column - } - - ((Collection)config.manyToManyTables).each { String table -> - strategy.addManyToManyTable table - } - - ((Map)config.manyToManyBelongsTos).each { String manyTable, String belongsTable -> - strategy.setManyToManyBelongsTo manyTable, belongsTable - } - - ((Collection)config.includeTables).each { String table -> - strategy.addIncludeTable table - } - - ((Collection)config.includeTableRegexes).each { String pattern -> - strategy.addIncludeTableRegex pattern - } - - ((Collection)config.includeTableAntPatterns).each { String pattern -> - strategy.addIncludeTableAntPattern pattern - } - - ((Collection)config.excludeTables).each { String table -> - strategy.addExcludeTable table - } - - ((Collection)config.excludeTableRegexes).each { String pattern -> - strategy.addExcludeTableRegex pattern - } - - ((Collection)config.excludeTableAntPatterns).each { String pattern -> - strategy.addExcludeTableAntPattern pattern - } - - ((Map>)config.excludeColumns).each { String table, List columns -> - strategy.addExcludeColumns table, columns - } - - ((Map>)config.excludeColumnRegexes).each { String table, List patterns -> - strategy.addExcludeColumnRegexes table, patterns - } - - ((Map>)config.excludeColumnAntPatterns).each { String table, List patterns -> - strategy.addExcludeColumnAntPatterns table, patterns - } - - ((Collection)config.mappedManyToManyTables).each { String table -> strategy.addMappedManyToManyTable table } - - strategy.alwaysMapManyToManyTables = config.alwaysMapManyToManyTables as boolean - - reenigne.execute() - } -} diff --git a/test-app/build.gradle b/test-app/build.gradle deleted file mode 100644 index dcac4a9..0000000 --- a/test-app/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -buildscript { - ext { - grailsVersion = project.grailsVersion - } - repositories { - mavenLocal() - maven { url 'https://repo.grails.org/grails/core' } - } - dependencies { - classpath "org.grails:grails-gradle-plugin:$grailsVersion" - classpath 'org.grails.plugins:db-reverse-engineer:4.0.0' - classpath 'org.grails.plugins:hibernate:4.3.10.7' - } -} - -plugins { - id 'io.spring.dependency-management' -} - -apply plugin: 'spring-boot' -apply from: '../gradle/testapp.gradle' - -dependencies { - compile 'org.grails.plugins:hibernate' - runtime 'mysql:mysql-connector-java:5.1.38' -} diff --git a/test-app/gradle.properties b/test-app/gradle.properties deleted file mode 100644 index 5951aac..0000000 --- a/test-app/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -grailsVersion=7.0.0-SNAPSHOT diff --git a/test-app/grails-app/conf/BuildConfig.groovy b/test-app/grails-app/conf/BuildConfig.groovy deleted file mode 100644 index 57b7477..0000000 --- a/test-app/grails-app/conf/BuildConfig.groovy +++ /dev/null @@ -1,30 +0,0 @@ -grails.servlet.version = '3.0' -grails.project.work.dir = 'target' -grails.project.target.level = 1.7 -grails.project.source.level = 1.7 - -grails.plugin.location.'db-reverse-engineer' = '..' - -grails.project.dependency.resolver = 'maven' -grails.project.dependency.resolution = { - inherits 'global' - log 'warn' - checksums true - legacyResolve false - - repositories { - inherits true - - mavenLocal() - grailsCentral() - mavenCentral() - } - - dependencies { - compile 'mysql:mysql-connector-java:5.1.38' - } - - plugins { - runtime ':hibernate4:4.3.8.1' - } -} diff --git a/test-app/grails-app/conf/application.yml b/test-app/grails-app/conf/application.yml deleted file mode 100644 index 0b1a495..0000000 --- a/test-app/grails-app/conf/application.yml +++ /dev/null @@ -1,40 +0,0 @@ ---- -grails: - profile: web - codegen: - defaultPackage: reveng.test -info: - app: - name: '@info.app.name@' - version: '@info.app.version@' - grailsVersion: '@info.app.grailsVersion@' -spring: - freemarker: - checkTemplateLocation: false - groovy: - template: - check-template-location: false - ---- -grails: - controllers: - defaultScope: singleton ---- -hibernate: - cache: - queries: false - use_query_cache: false - use_second_level_cache: false - format_sql: true - use_sql_comments: true - -environments: - development: - dataSource: - dbCreate: none - dialect: org.hibernate.dialect.MySQL5InnoDBDialect - driverClassName: com.mysql.jdbc.Driver - password: reveng - pooled: true - url: jdbc:mysql://localhost/reveng - username: reveng diff --git a/test-app/grails-app/conf/logback.groovy b/test-app/grails-app/conf/logback.groovy deleted file mode 100644 index 4118f24..0000000 --- a/test-app/grails-app/conf/logback.groovy +++ /dev/null @@ -1,24 +0,0 @@ -import grails.util.BuildSettings -import grails.util.Environment - -appender('STDOUT', ConsoleAppender) { - encoder(PatternLayoutEncoder) { - pattern = '%level %logger - %msg%n' - } -} - -root ERROR, ['STDOUT'] - -File targetDir = BuildSettings.TARGET_DIR -if (Environment.developmentMode && targetDir) { - - appender('FULL_STACKTRACE', FileAppender) { - file = "$targetDir/stacktrace.log" - append = true - encoder(PatternLayoutEncoder) { - pattern = '%level %logger - %msg%n' - } - } - - logger 'StackTrace', ERROR, ['FULL_STACKTRACE'], false -} diff --git a/test-app/grails-app/init/reveng/test/Application.groovy b/test-app/grails-app/init/reveng/test/Application.groovy deleted file mode 100644 index 1671812..0000000 --- a/test-app/grails-app/init/reveng/test/Application.groovy +++ /dev/null @@ -1,10 +0,0 @@ -package reveng.test - -import grails.boot.GrailsApp -import grails.boot.config.GrailsAutoConfiguration - -class Application extends GrailsAutoConfiguration { - static void main(String[] args) { - GrailsApp.run Application, args - } -} diff --git a/version.txt b/version.txt deleted file mode 100644 index 0062ac9..0000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -5.0.0