├── .gitattributes ├── .github └── workflows │ ├── ci.yaml │ ├── codeql.yml │ ├── coveralls.yaml │ ├── site.yaml │ ├── sonar.yaml │ └── sonatype.yaml ├── .gitignore ├── .mvn ├── extensions.xml ├── maven.config ├── settings.xml └── wrapper │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── LICENSE ├── LICENSE_HEADER ├── NOTICE ├── README.md ├── format.xml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── renovate.json └── src ├── main └── java │ └── org │ └── mybatis │ └── scripting │ └── velocity │ ├── Driver.java │ ├── FastLinkedList.java │ ├── InDirective.java │ ├── ParameterMappingCollector.java │ ├── ParameterMappingSourceParser.java │ ├── RepeatDirective.java │ ├── SQLScriptSource.java │ ├── SetDirective.java │ ├── TrimDirective.java │ ├── VelocityFacade.java │ ├── VelocityLanguageDriver.java │ ├── VelocityLanguageDriverConfig.java │ └── WhereDirective.java ├── site ├── site.xml └── xdoc │ └── index.xml └── test ├── java └── org │ └── mybatis │ └── scripting │ └── velocity │ ├── InDirectiveTest.java │ ├── TrimDirectiveTest.java │ ├── VelocityLanguageDriverConfigTest.java │ ├── WhereDirectiveTest.java │ └── use │ ├── CustomUserDirective.java │ ├── EnumBinder.java │ ├── EnumWrapper.java │ ├── Name.java │ ├── Parameter.java │ ├── TrailingWildCardFormatter.java │ └── VelocityLanguageTest.java └── resources ├── log4j.properties ├── mybatis-velocity-custom.properties ├── mybatis-velocity-empty.properties ├── mybatis-velocity-legacy.properties ├── mybatis-velocity.properties └── org └── mybatis └── scripting └── velocity └── use ├── CreateDB.sql ├── MapperConfig.xml └── mapper.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [workflow_dispatch, push, pull_request] 4 | 5 | permissions: read-all 6 | 7 | jobs: 8 | test: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | cache: [maven] 13 | distribution: [temurin] 14 | java: [17, 21, 24, 25-ea] 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | fail-fast: false 17 | max-parallel: 4 18 | name: Test JDK ${{ matrix.java }}, ${{ matrix.os }} 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - name: Set up JDK ${{ matrix.java }} ${{ matrix.distribution }} 23 | uses: actions/setup-java@v4 24 | with: 25 | java-version: ${{ matrix.java }} 26 | distribution: ${{ matrix.distribution }} 27 | cache: ${{ matrix.cache }} 28 | - name: Test with Maven 29 | run: ./mvnw test -B -V --no-transfer-progress -D"license.skip=true" 30 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '34 6 * * 6' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 15 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'java-kotlin' ] 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Setup Java 31 | uses: actions/setup-java@v4 32 | with: 33 | cache: maven 34 | distribution: 'temurin' 35 | java-version: 21 36 | 37 | - name: Initialize CodeQL 38 | uses: github/codeql-action/init@v3 39 | with: 40 | languages: ${{ matrix.language }} 41 | queries: +security-and-quality 42 | 43 | - name: Autobuild 44 | uses: github/codeql-action/autobuild@v3 45 | 46 | - name: Perform CodeQL Analysis 47 | uses: github/codeql-action/analyze@v3 48 | with: 49 | category: "/language:${{ matrix.language }}" 50 | -------------------------------------------------------------------------------- /.github/workflows/coveralls.yaml: -------------------------------------------------------------------------------- 1 | name: Coveralls 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: read-all 6 | 7 | jobs: 8 | build: 9 | if: github.repository_owner == 'mybatis' 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - name: Set up JDK 14 | uses: actions/setup-java@v4 15 | with: 16 | cache: maven 17 | distribution: temurin 18 | java-version: 21 19 | - name: Report Coverage to Coveralls for Pull Requests 20 | if: github.event_name == 'pull_request' 21 | run: ./mvnw -B -V test jacoco:report coveralls:report -q -Dlicense.skip=true -DrepoToken=$GITHUB_TOKEN -DserviceName=github -DpullRequest=$PR_NUMBER --no-transfer-progress 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | PR_NUMBER: ${{ github.event.number }} 25 | - name: Report Coverage to Coveralls for General Push 26 | if: github.event_name == 'push' 27 | run: ./mvnw -B -V test jacoco:report coveralls:report -q -Dlicense.skip=true -DrepoToken=$GITHUB_TOKEN -DserviceName=github --no-transfer-progress 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/site.yaml: -------------------------------------------------------------------------------- 1 | name: Site 2 | 3 | on: 4 | push: 5 | branches: 6 | - site 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | build: 13 | if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up JDK 18 | uses: actions/setup-java@v4 19 | with: 20 | cache: maven 21 | distribution: temurin 22 | java-version: 21 23 | - name: Build site 24 | run: ./mvnw site site:stage -DskipTests -Dlicense.skip=true -B -V --no-transfer-progress --settings ./.mvn/settings.xml 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | NVD_API_KEY: ${{ secrets.NVD_API_KEY }} 28 | - name: Deploy Site to gh-pages 29 | uses: JamesIves/github-pages-deploy-action@v4 30 | with: 31 | branch: gh-pages 32 | folder: target/staging 33 | -------------------------------------------------------------------------------- /.github/workflows/sonar.yaml: -------------------------------------------------------------------------------- 1 | name: SonarCloud 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: read-all 9 | 10 | jobs: 11 | build: 12 | if: github.repository_owner == 'mybatis' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | # Disabling shallow clone is recommended for improving relevancy of reporting 18 | fetch-depth: 0 19 | - name: Set up JDK 20 | uses: actions/setup-java@v4 21 | with: 22 | cache: maven 23 | distribution: temurin 24 | java-version: 21 25 | - name: Analyze with SonarCloud 26 | run: ./mvnw verify jacoco:report sonar:sonar -B -V -Dsonar.projectKey=mybatis_velocity-scripting -Dsonar.organization=mybatis -Dsonar.host.url=https://sonarcloud.io -Dsonar.token=$SONAR_TOKEN -Dlicense.skip=true --no-transfer-progress 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/sonatype.yaml: -------------------------------------------------------------------------------- 1 | name: Sonatype 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: read-all 9 | 10 | jobs: 11 | build: 12 | if: github.repository_owner == 'mybatis' && ! contains(toJSON(github.event.head_commit.message), '[maven-release-plugin]') 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up JDK 17 | uses: actions/setup-java@v4 18 | with: 19 | cache: maven 20 | distribution: temurin 21 | java-version: 21 22 | - name: Deploy to Sonatype 23 | run: ./mvnw deploy -DskipTests -B -V --no-transfer-progress --settings ./.mvn/settings.xml -Dlicense.skip=true 24 | env: 25 | CI_DEPLOY_USERNAME: ${{ secrets.CI_DEPLOY_USERNAME }} 26 | CI_DEPLOY_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.settings 2 | /.classpath 3 | /.project 4 | /target 5 | .mvn/wrapper/maven-wrapper.jar 6 | 7 | .idea 8 | *.iml 9 | .github/keys/ 10 | release.properties 11 | pom.xml.releaseBackup 12 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | fr.jcgay.maven 22 | maven-profiler 23 | 3.3 24 | 25 | 26 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5 2 | -Daether.connector.smartChecksums=false 3 | -------------------------------------------------------------------------------- /.mvn/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | 24 | 25 | central 26 | ${env.CI_DEPLOY_USERNAME} 27 | ${env.CI_DEPLOY_PASSWORD} 28 | 29 | 30 | 31 | 32 | gh-pages-scm 33 | 34 | branch 35 | gh-pages 36 | 37 | 38 | 39 | 40 | 41 | github 42 | ${env.GITHUB_TOKEN} 43 | 44 | 45 | 46 | 47 | nvd 48 | ${env.NVD_API_KEY} 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * https://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.net.Authenticator; 23 | import java.net.PasswordAuthentication; 24 | import java.net.URI; 25 | import java.net.URL; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.nio.file.Paths; 29 | import java.nio.file.StandardCopyOption; 30 | import java.util.concurrent.ThreadLocalRandom; 31 | 32 | public final class MavenWrapperDownloader { 33 | private static final String WRAPPER_VERSION = "3.3.2"; 34 | 35 | private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE")); 36 | 37 | public static void main(String[] args) { 38 | log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION); 39 | 40 | if (args.length != 2) { 41 | System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing"); 42 | System.exit(1); 43 | } 44 | 45 | try { 46 | log(" - Downloader started"); 47 | final URL wrapperUrl = URI.create(args[0]).toURL(); 48 | final String jarPath = args[1].replace("..", ""); // Sanitize path 49 | final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize(); 50 | downloadFileFromURL(wrapperUrl, wrapperJarPath); 51 | log("Done"); 52 | } catch (IOException e) { 53 | System.err.println("- Error downloading: " + e.getMessage()); 54 | if (VERBOSE) { 55 | e.printStackTrace(); 56 | } 57 | System.exit(1); 58 | } 59 | } 60 | 61 | private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath) 62 | throws IOException { 63 | log(" - Downloading to: " + wrapperJarPath); 64 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 65 | final String username = System.getenv("MVNW_USERNAME"); 66 | final char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 67 | Authenticator.setDefault(new Authenticator() { 68 | @Override 69 | protected PasswordAuthentication getPasswordAuthentication() { 70 | return new PasswordAuthentication(username, password); 71 | } 72 | }); 73 | } 74 | Path temp = wrapperJarPath 75 | .getParent() 76 | .resolve(wrapperJarPath.getFileName() + "." 77 | + Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp"); 78 | try (InputStream inStream = wrapperUrl.openStream()) { 79 | Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING); 80 | Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING); 81 | } finally { 82 | Files.deleteIfExists(temp); 83 | } 84 | log(" - Downloader complete"); 85 | } 86 | 87 | private static void log(String msg) { 88 | if (VERBOSE) { 89 | System.out.println(msg); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=source 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.10/apache-maven-3.9.10-bin.zip 20 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | https://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | https://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE_HEADER: -------------------------------------------------------------------------------- 1 | Copyright ${license.git.copyrightYears} the original author or authors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | MyBatis Velocity 2 | Copyright 2011-2024 3 | 4 | This product includes software developed by 5 | The MyBatis Team (http://mybatis.org/). 6 | 7 | Apache Velocity 8 | This product includes software developed by 9 | The Apache Software Foundation (http://www.apache.org/). 10 | 11 | Copyright 2010 The Apache Software Foundation 12 | 13 | Licensed under the Apache License, Version 2.0 (the "License"); 14 | you may not use this file except in compliance with the License. 15 | You may obtain a copy of the License at 16 | 17 | http://www.apache.org/licenses/LICENSE-2.0 18 | 19 | Unless required by applicable law or agreed to in writing, software 20 | distributed under the License is distributed on an "AS IS" BASIS, 21 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | See the License for the specific language governing permissions and 23 | limitations under the License. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MyBatis Velocity Support 2 | ======================== 3 | 4 | [![Build Status](https://github.com/mybatis/velocity-scripting/workflows/Java%20CI/badge.svg?branch=master)](https://github.com/mybatis/velocity-scripting/actions?query=workflow%3A%22Java+CI%22) 5 | [![Coverage Status](https://coveralls.io/repos/mybatis/velocity-scripting/badge.svg?branch=master&service=github)](https://coveralls.io/github/mybatis/velocity-scripting?branch=master) 6 | [![Maven central](https://maven-badges.herokuapp.com/maven-central/org.mybatis.scripting/mybatis-velocity/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.mybatis.scripting/mybatis-velocity) 7 | [![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/https/oss.sonatype.org/org.mybatis.scripting/mybatis-velocity.svg)](https://oss.sonatype.org/content/repositories/snapshots/org/mybatis/scripting/mybatis-velocity/) 8 | [![License](https://img.shields.io/:license-apache-brightgreen.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 9 | 10 | ![mybatis-velocity](https://mybatis.org/images/mybatis-logo.png) 11 | 12 | Mybatis Velocity Scripting Support. 13 | 14 | Requirements 15 | ---------- 16 | 17 | * master(2.1.x) : MyBatis 3.5+, Velocity 2.1+ and Java 8+ 18 | * 2.0.x : MyBatis 3.4+, Velocity 2.0 and Java 7+ 19 | 20 | Essentials 21 | ---------- 22 | 23 | * [See the docs](https://mybatis.org/velocity-scripting/) 24 | -------------------------------------------------------------------------------- /format.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ]; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ]; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ]; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ]; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false 54 | darwin=false 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true ;; 59 | Darwin*) 60 | darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | JAVA_HOME="$(/usr/libexec/java_home)" 66 | export JAVA_HOME 67 | else 68 | JAVA_HOME="/Library/Java/Home" 69 | export JAVA_HOME 70 | fi 71 | fi 72 | ;; 73 | esac 74 | 75 | if [ -z "$JAVA_HOME" ]; then 76 | if [ -r /etc/gentoo-release ]; then 77 | JAVA_HOME=$(java-config --jre-home) 78 | fi 79 | fi 80 | 81 | # For Cygwin, ensure paths are in UNIX format before anything is touched 82 | if $cygwin; then 83 | [ -n "$JAVA_HOME" ] \ 84 | && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 85 | [ -n "$CLASSPATH" ] \ 86 | && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 87 | fi 88 | 89 | # For Mingw, ensure paths are in UNIX format before anything is touched 90 | if $mingw; then 91 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ 92 | && JAVA_HOME="$( 93 | cd "$JAVA_HOME" || ( 94 | echo "cannot cd into $JAVA_HOME." >&2 95 | exit 1 96 | ) 97 | pwd 98 | )" 99 | fi 100 | 101 | if [ -z "$JAVA_HOME" ]; then 102 | javaExecutable="$(which javac)" 103 | if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then 104 | # readlink(1) is not available as standard on Solaris 10. 105 | readLink=$(which readlink) 106 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 107 | if $darwin; then 108 | javaHome="$(dirname "$javaExecutable")" 109 | javaExecutable="$(cd "$javaHome" && pwd -P)/javac" 110 | else 111 | javaExecutable="$(readlink -f "$javaExecutable")" 112 | fi 113 | javaHome="$(dirname "$javaExecutable")" 114 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 115 | JAVA_HOME="$javaHome" 116 | export JAVA_HOME 117 | fi 118 | fi 119 | fi 120 | 121 | if [ -z "$JAVACMD" ]; then 122 | if [ -n "$JAVA_HOME" ]; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD="$JAVA_HOME/jre/sh/java" 126 | else 127 | JAVACMD="$JAVA_HOME/bin/java" 128 | fi 129 | else 130 | JAVACMD="$( 131 | \unset -f command 2>/dev/null 132 | \command -v java 133 | )" 134 | fi 135 | fi 136 | 137 | if [ ! -x "$JAVACMD" ]; then 138 | echo "Error: JAVA_HOME is not defined correctly." >&2 139 | echo " We cannot execute $JAVACMD" >&2 140 | exit 1 141 | fi 142 | 143 | if [ -z "$JAVA_HOME" ]; then 144 | echo "Warning: JAVA_HOME environment variable is not set." >&2 145 | fi 146 | 147 | # traverses directory structure from process work directory to filesystem root 148 | # first directory with .mvn subdirectory is considered project base directory 149 | find_maven_basedir() { 150 | if [ -z "$1" ]; then 151 | echo "Path not specified to find_maven_basedir" >&2 152 | return 1 153 | fi 154 | 155 | basedir="$1" 156 | wdir="$1" 157 | while [ "$wdir" != '/' ]; do 158 | if [ -d "$wdir"/.mvn ]; then 159 | basedir=$wdir 160 | break 161 | fi 162 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 163 | if [ -d "${wdir}" ]; then 164 | wdir=$( 165 | cd "$wdir/.." || exit 1 166 | pwd 167 | ) 168 | fi 169 | # end of workaround 170 | done 171 | printf '%s' "$( 172 | cd "$basedir" || exit 1 173 | pwd 174 | )" 175 | } 176 | 177 | # concatenates all lines of a file 178 | concat_lines() { 179 | if [ -f "$1" ]; then 180 | # Remove \r in case we run on Windows within Git Bash 181 | # and check out the repository with auto CRLF management 182 | # enabled. Otherwise, we may read lines that are delimited with 183 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 184 | # splitting rules. 185 | tr -s '\r\n' ' ' <"$1" 186 | fi 187 | } 188 | 189 | log() { 190 | if [ "$MVNW_VERBOSE" = true ]; then 191 | printf '%s\n' "$1" 192 | fi 193 | } 194 | 195 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 196 | if [ -z "$BASE_DIR" ]; then 197 | exit 1 198 | fi 199 | 200 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 201 | export MAVEN_PROJECTBASEDIR 202 | log "$MAVEN_PROJECTBASEDIR" 203 | 204 | ########################################################################################## 205 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 206 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 207 | ########################################################################################## 208 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 209 | if [ -r "$wrapperJarPath" ]; then 210 | log "Found $wrapperJarPath" 211 | else 212 | log "Couldn't find $wrapperJarPath, downloading it ..." 213 | 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" 216 | else 217 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" 218 | fi 219 | while IFS="=" read -r key value; do 220 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 221 | safeValue=$(echo "$value" | tr -d '\r') 222 | case "$key" in wrapperUrl) 223 | wrapperUrl="$safeValue" 224 | break 225 | ;; 226 | esac 227 | done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 228 | log "Downloading from: $wrapperUrl" 229 | 230 | if $cygwin; then 231 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 232 | fi 233 | 234 | if command -v wget >/dev/null; then 235 | log "Found wget ... using wget" 236 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 237 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 238 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 239 | else 240 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | fi 242 | elif command -v curl >/dev/null; then 243 | log "Found curl ... using curl" 244 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 245 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 246 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 247 | else 248 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 249 | fi 250 | else 251 | log "Falling back to using Java to download" 252 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 253 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 254 | # For Cygwin, switch paths to Windows format before running javac 255 | if $cygwin; then 256 | javaSource=$(cygpath --path --windows "$javaSource") 257 | javaClass=$(cygpath --path --windows "$javaClass") 258 | fi 259 | if [ -e "$javaSource" ]; then 260 | if [ ! -e "$javaClass" ]; then 261 | log " - Compiling MavenWrapperDownloader.java ..." 262 | ("$JAVA_HOME/bin/javac" "$javaSource") 263 | fi 264 | if [ -e "$javaClass" ]; then 265 | log " - Running MavenWrapperDownloader.java ..." 266 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 267 | fi 268 | fi 269 | fi 270 | fi 271 | ########################################################################################## 272 | # End of extension 273 | ########################################################################################## 274 | 275 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 276 | wrapperSha256Sum="" 277 | while IFS="=" read -r key value; do 278 | case "$key" in wrapperSha256Sum) 279 | wrapperSha256Sum=$value 280 | break 281 | ;; 282 | esac 283 | done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 284 | if [ -n "$wrapperSha256Sum" ]; then 285 | wrapperSha256Result=false 286 | if command -v sha256sum >/dev/null; then 287 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then 288 | wrapperSha256Result=true 289 | fi 290 | elif command -v shasum >/dev/null; then 291 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then 292 | wrapperSha256Result=true 293 | fi 294 | else 295 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 296 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 297 | exit 1 298 | fi 299 | if [ $wrapperSha256Result = false ]; then 300 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 301 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 302 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 303 | exit 1 304 | fi 305 | fi 306 | 307 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 308 | 309 | # For Cygwin, switch paths to Windows format before running java 310 | if $cygwin; then 311 | [ -n "$JAVA_HOME" ] \ 312 | && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 313 | [ -n "$CLASSPATH" ] \ 314 | && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 315 | [ -n "$MAVEN_PROJECTBASEDIR" ] \ 316 | && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 317 | fi 318 | 319 | # Provide a "standardized" way to retrieve the CLI args that will 320 | # work with both Windows and non-Windows executions. 321 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 322 | export MAVEN_CMD_LINE_ARGS 323 | 324 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 325 | 326 | # shellcheck disable=SC2086 # safe args 327 | exec "$JAVACMD" \ 328 | $MAVEN_OPTS \ 329 | $MAVEN_DEBUG_OPTS \ 330 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 331 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 332 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 333 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. >&2 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. >&2 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. >&2 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. >&2 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ 164 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 165 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 166 | " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 167 | " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 168 | " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 169 | " exit 1;"^ 170 | "}"^ 171 | "}" 172 | if ERRORLEVEL 1 goto error 173 | ) 174 | 175 | @REM Provide a "standardized" way to retrieve the CLI args that will 176 | @REM work with both Windows and non-Windows executions. 177 | set MAVEN_CMD_LINE_ARGS=%* 178 | 179 | %MAVEN_JAVA_EXE% ^ 180 | %JVM_CONFIG_MAVEN_PROPS% ^ 181 | %MAVEN_OPTS% ^ 182 | %MAVEN_DEBUG_OPTS% ^ 183 | -classpath %WRAPPER_JAR% ^ 184 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 185 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 186 | if ERRORLEVEL 1 goto error 187 | goto end 188 | 189 | :error 190 | set ERROR_CODE=1 191 | 192 | :end 193 | @endlocal & set ERROR_CODE=%ERROR_CODE% 194 | 195 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 196 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 197 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 198 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 199 | :skipRcPost 200 | 201 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 202 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 203 | 204 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 205 | 206 | cmd /C exit /B %ERROR_CODE% 207 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 4.0.0 22 | 23 | 24 | org.mybatis 25 | mybatis-parent 26 | 50 27 | 28 | 29 | 30 | org.mybatis.scripting 31 | mybatis-velocity 32 | 2.3.1-SNAPSHOT 33 | 34 | MyBatis Velocity 35 | Velocity support for MyBatis 36 | http://www.mybatis.org/scripting/velocity/ 37 | 38 | 2012 39 | 40 | 41 | scm:git:ssh://git@github.com/mybatis/velocity-scripting.git 42 | scm:git:ssh://git@github.com/mybatis/velocity-scripting.git 43 | HEAD 44 | http://github.com/mybatis/velocity-scripting/ 45 | 46 | 47 | GitHub Issue Management 48 | https://github.com/mybatis/velocity-scripting/issues 49 | 50 | 51 | GitHub Actions 52 | https://github.com/mybatis/velocity-scripting/actions 53 | 54 | 55 | 56 | gh-pages-scm 57 | Mybatis GitHub Pages 58 | scm:git:ssh://git@github.com/mybatis/velocity-scripting.git 59 | 60 | 61 | 62 | 63 | 64 | 11 65 | 11 66 | 67 | org.mybatis.scripting.velocity.* 68 | Velocity 69 | org.mybatis.scripting.velocity 70 | 2.0 71 | 72 | 73 | 1727305066 74 | 75 | --add-opens java.base/java.util=ALL-UNNAMED 76 | 77 | 78 | 79 | 80 | org.mybatis 81 | mybatis 82 | 3.5.19 83 | 84 | 85 | org.apache.velocity 86 | velocity-engine-core 87 | 2.4.1 88 | 89 | 90 | org.apache.commons 91 | commons-text 92 | 1.13.1 93 | 94 | 95 | 96 | 97 | org.junit.jupiter 98 | junit-jupiter-engine 99 | 5.13.3 100 | test 101 | 102 | 103 | org.hsqldb 104 | hsqldb 105 | 2.7.4 106 | test 107 | 108 | 109 | org.slf4j 110 | slf4j-simple 111 | 2.0.17 112 | test 113 | 114 | 115 | 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-release-plugin 121 | 122 | release,bundle 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/Driver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | /** 19 | * @deprecated Since 2.1.0, recommend to use the {@link VelocityLanguageDriver}. 20 | */ 21 | @Deprecated 22 | public class Driver extends VelocityLanguageDriver { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/FastLinkedList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.Serializable; 19 | 20 | /** 21 | * Paranoiac small and fast forward only list 22 | */ 23 | public final class FastLinkedList implements Serializable { 24 | 25 | private static final long serialVersionUID = 1L; 26 | 27 | private Node first; 28 | 29 | private Node last; 30 | 31 | public FastLinkedList() { 32 | this.last = this.first; 33 | } 34 | 35 | public boolean isEmpty() { 36 | return this.first == null; 37 | } 38 | 39 | public Node start() { 40 | return this.first; 41 | } 42 | 43 | public void add(E e) { 44 | final Node n = new Node(e); 45 | if (this.first == null) { 46 | this.first = n; 47 | this.last = this.first; 48 | } else { 49 | this.last.next = n; 50 | this.last = n; 51 | } 52 | } 53 | 54 | public final class Node { 55 | 56 | final E data; 57 | Node next; 58 | 59 | public Node(E newData) { 60 | this.data = newData; 61 | } 62 | 63 | public boolean hasNext() { 64 | return this.next != null; 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/InDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.IOException; 19 | import java.io.StringWriter; 20 | import java.io.Writer; 21 | import java.util.Iterator; 22 | 23 | import org.apache.velocity.context.InternalContextAdapter; 24 | import org.apache.velocity.exception.TemplateInitException; 25 | import org.apache.velocity.exception.VelocityException; 26 | import org.apache.velocity.runtime.RuntimeServices; 27 | import org.apache.velocity.runtime.directive.StopCommand; 28 | import org.apache.velocity.runtime.parser.node.ASTReference; 29 | import org.apache.velocity.runtime.parser.node.ASTStringLiteral; 30 | import org.apache.velocity.runtime.parser.node.Node; 31 | import org.apache.velocity.runtime.parser.node.StandardParserTreeConstants; 32 | import org.apache.velocity.util.introspection.Info; 33 | 34 | /** 35 | * #in($collection $item COLUMN). 36 | */ 37 | public class InDirective extends RepeatDirective { 38 | 39 | /** 40 | * Immutable fields 41 | */ 42 | private String var; 43 | 44 | private String open = "("; 45 | 46 | private String close = ")"; 47 | 48 | private String separator = ", "; 49 | 50 | private String column = ""; 51 | 52 | @Override 53 | public String getName() { 54 | return "in"; 55 | } 56 | 57 | @Override 58 | public void init(RuntimeServices rs, InternalContextAdapter context, Node node) { 59 | super.init(rs, context, node); 60 | final int n = node.jjtGetNumChildren() - 1; 61 | for (int i = 1; i < n; i++) { 62 | Node child = node.jjtGetChild(i); 63 | if (i == 1) { 64 | if (child.getType() == StandardParserTreeConstants.JJTREFERENCE) { 65 | this.var = ((ASTReference) child).getRootString(); 66 | } else { 67 | throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn()); 68 | } 69 | } else if (child.getType() == StandardParserTreeConstants.JJTSTRINGLITERAL) { 70 | String value = (String) ((ASTStringLiteral) child).value(context); 71 | if (i == 2) { 72 | this.column = value; 73 | } 74 | } else { 75 | throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn()); 76 | } 77 | } 78 | this.uberInfo = new Info(this.getTemplateName(), getLine(), getColumn()); 79 | } 80 | 81 | @Override 82 | public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException { 83 | Object listObject = node.jjtGetChild(0).value(context); 84 | if (listObject == null) { 85 | return false; 86 | } 87 | 88 | Iterator iterator = null; 89 | 90 | try { 91 | iterator = this.rsvc.getUberspect().getIterator(listObject, this.uberInfo); 92 | } catch (RuntimeException e) { 93 | throw e; 94 | } catch (Exception ee) { 95 | String msg = "Error getting iterator for #in at " + this.uberInfo; 96 | this.rsvc.getLog().error(msg, ee); 97 | throw new VelocityException(msg, ee); 98 | } 99 | 100 | if (iterator == null) { 101 | throw new VelocityException("Invalid collection"); 102 | } 103 | 104 | int counter = 0; 105 | Object o = context.get(this.var); 106 | 107 | ParameterMappingCollector collector = (ParameterMappingCollector) context 108 | .get(SQLScriptSource.MAPPING_COLLECTOR_KEY); 109 | String savedItemKey = collector.getItemKey(); 110 | collector.setItemKey(this.var); 111 | RepeatScope foreach = new RepeatScope(this, context.get(getName()), this.var); 112 | context.put(getName(), foreach); 113 | 114 | NullHolderContext nullHolderContext = null; 115 | Object value = null; 116 | StringWriter buffer = new StringWriter(); 117 | 118 | while (iterator.hasNext()) { 119 | 120 | if (counter % MAX_IN_CLAUSE_SIZE == 0) { 121 | buffer.append(this.open); // Group begins 122 | buffer.append(this.column); 123 | buffer.append(" IN "); 124 | buffer.append(this.open); // In starts 125 | } 126 | 127 | value = iterator.next(); 128 | put(context, this.var, value); 129 | foreach.index++; 130 | foreach.hasNext = iterator.hasNext(); 131 | 132 | try { 133 | if (value == null) { 134 | if (nullHolderContext == null) { 135 | nullHolderContext = new NullHolderContext(this.var, context); 136 | } 137 | node.jjtGetChild(node.jjtGetNumChildren() - 1).render(nullHolderContext, buffer); 138 | } else { 139 | node.jjtGetChild(node.jjtGetNumChildren() - 1).render(context, buffer); 140 | } 141 | } catch (StopCommand stop) { 142 | if (stop.isFor(this)) { 143 | break; 144 | } 145 | clean(context, o, collector, savedItemKey); 146 | // close does not perform any action and this is here 147 | // to avoid eclipse reporting possible leak. 148 | buffer.close(); 149 | throw stop; 150 | } 151 | counter++; 152 | 153 | if ((counter > 0 && counter % MAX_IN_CLAUSE_SIZE == 0) || !iterator.hasNext()) { 154 | buffer.append(this.close); // In ends 155 | buffer.append(this.close); // Group ends 156 | if (iterator.hasNext()) { 157 | buffer.append(" OR "); 158 | } 159 | } else if (iterator.hasNext()) { 160 | buffer.append(this.separator); 161 | } 162 | 163 | } 164 | String content = buffer.toString().trim(); 165 | if (!"".equals(content)) { 166 | writer.append(this.open); 167 | writer.append(content); 168 | writer.append(this.close); 169 | } else { 170 | writer.append(this.open); 171 | writer.append(this.open); 172 | writer.append(this.column); 173 | writer.append(" NOT IN "); 174 | writer.append(this.open); 175 | writer.append(" NULL "); 176 | writer.append(this.close); 177 | writer.append(this.close); 178 | writer.append(this.close); 179 | } 180 | clean(context, o, collector, savedItemKey); 181 | return true; 182 | } 183 | 184 | @Override 185 | public int getType() { 186 | return BLOCK; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/ParameterMappingCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | import org.apache.ibatis.mapping.ParameterMapping; 23 | import org.apache.ibatis.session.Configuration; 24 | 25 | public class ParameterMappingCollector { 26 | 27 | private final ParameterMapping[] parameterMappingSources; 28 | private final List parameterMappings = new ArrayList<>(); 29 | private final Map context; 30 | private final Configuration configuration; 31 | 32 | private int uid = 0; 33 | private String itemKey; 34 | 35 | public ParameterMappingCollector(ParameterMapping[] newParameterMappingSources, Map newContext, 36 | Configuration newConfiguration) { 37 | this.parameterMappingSources = newParameterMappingSources; 38 | this.context = newContext; 39 | this.configuration = newConfiguration; 40 | } 41 | 42 | public void setItemKey(String value) { 43 | this.itemKey = value; 44 | } 45 | 46 | public String getItemKey() { 47 | return this.itemKey; 48 | } 49 | 50 | public String g(int mapping) { 51 | ParameterMapping parameterMapping = this.parameterMappingSources[mapping]; 52 | PropertyInfo vi = getPropertyInfo(parameterMapping.getProperty()); 53 | if (vi.isIterable) { 54 | parameterMapping = itemize(parameterMapping, vi); 55 | this.context.put(vi.root, this.context.get(this.itemKey)); 56 | } 57 | this.parameterMappings.add(parameterMapping); 58 | return "?"; 59 | } 60 | 61 | public List getParameterMappings() { 62 | return this.parameterMappings; 63 | } 64 | 65 | private ParameterMapping itemize(ParameterMapping source, PropertyInfo var) { 66 | StringBuilder sb = new StringBuilder().append("_RPTITEM_").append(this.uid++); 67 | var.root = sb.toString(); 68 | String propertyName = sb.append(var.path).toString(); 69 | ParameterMapping.Builder builder = new ParameterMapping.Builder(this.configuration, propertyName, 70 | source.getJavaType()); 71 | builder.expression(source.getExpression()).jdbcType(source.getJdbcType()).jdbcTypeName(source.getJdbcTypeName()) 72 | .mode(source.getMode()).numericScale(source.getNumericScale()).resultMapId(source.getResultMapId()) 73 | .typeHandler(source.getTypeHandler()); 74 | return builder.build(); 75 | } 76 | 77 | private PropertyInfo getPropertyInfo(String name) { 78 | PropertyInfo i = new PropertyInfo(); 79 | if (name != null) { 80 | int p = name.indexOf('.'); 81 | if (p == -1) { 82 | i.root = name; 83 | } else { 84 | i.root = name.substring(0, p); 85 | i.path = name.substring(p); 86 | } 87 | } 88 | i.isIterable = this.itemKey != null && this.itemKey.equals(i.root); 89 | return i; 90 | } 91 | 92 | static class PropertyInfo { 93 | boolean isIterable = false; 94 | String root = ""; 95 | String path = ""; 96 | 97 | public PropertyInfo() { 98 | // Prevent synthetic access 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/ParameterMappingSourceParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | import org.apache.ibatis.builder.BaseBuilder; 23 | import org.apache.ibatis.builder.BuilderException; 24 | import org.apache.ibatis.builder.ParameterExpression; 25 | import org.apache.ibatis.mapping.ParameterMapping; 26 | import org.apache.ibatis.parsing.GenericTokenParser; 27 | import org.apache.ibatis.parsing.TokenHandler; 28 | import org.apache.ibatis.reflection.DefaultReflectorFactory; 29 | import org.apache.ibatis.reflection.MetaClass; 30 | import org.apache.ibatis.reflection.ReflectorFactory; 31 | import org.apache.ibatis.session.Configuration; 32 | import org.apache.ibatis.type.JdbcType; 33 | 34 | public class ParameterMappingSourceParser { 35 | 36 | private static final String VALID_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName"; 37 | 38 | private final String sql; 39 | 40 | private final ParameterMapping[] parameterMappingSources; 41 | 42 | public ParameterMappingSourceParser(Configuration configuration, String script, Class parameterType) { 43 | ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType); 44 | GenericTokenParser parser = new GenericTokenParser("@{", "}", handler); 45 | this.sql = parser.parse(script); 46 | this.parameterMappingSources = handler.getParameterMappingSources(); 47 | } 48 | 49 | public ParameterMapping[] getParameterMappingSources() { 50 | return this.parameterMappingSources; 51 | } 52 | 53 | public String getSql() { 54 | return this.sql; 55 | } 56 | 57 | private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler { 58 | 59 | private final List parameterMappings = new ArrayList<>(); 60 | private final Class parameterType; 61 | 62 | public ParameterMappingTokenHandler(Configuration newConfiguration, Class newParameterType) { 63 | super(newConfiguration); 64 | this.parameterType = newParameterType; 65 | } 66 | 67 | public ParameterMapping[] getParameterMappingSources() { 68 | return this.parameterMappings.toArray(new ParameterMapping[this.parameterMappings.size()]); 69 | } 70 | 71 | @Override 72 | public String handleToken(String content) { 73 | int index = this.parameterMappings.size(); 74 | ParameterMapping pm = buildParameterMapping(content); 75 | this.parameterMappings.add(pm); 76 | return new StringBuilder().append('$').append(SQLScriptSource.MAPPING_COLLECTOR_KEY).append(".g(").append(index) 77 | .append(")").toString(); 78 | } 79 | 80 | private ParameterMapping buildParameterMapping(String content) { 81 | Map propertiesMap = parseParameterMapping(content); 82 | String property = propertiesMap.get("property"); 83 | String jdbcType = propertiesMap.get("jdbcType"); 84 | Class propertyType; 85 | if (this.typeHandlerRegistry.hasTypeHandler(this.parameterType)) { 86 | propertyType = this.parameterType; 87 | } else if (JdbcType.CURSOR.name().equals(jdbcType)) { 88 | propertyType = java.sql.ResultSet.class; 89 | } else if (property != null) { 90 | ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); 91 | MetaClass metaClass = MetaClass.forClass(this.parameterType, reflectorFactory); 92 | if (metaClass.hasGetter(property)) { 93 | propertyType = metaClass.getGetterType(property); 94 | } else { 95 | propertyType = Object.class; 96 | } 97 | } else { 98 | propertyType = Object.class; 99 | } 100 | ParameterMapping.Builder builder = new ParameterMapping.Builder(this.configuration, property, propertyType); 101 | if (jdbcType != null) { 102 | builder.jdbcType(resolveJdbcType(jdbcType)); 103 | } 104 | Class javaType = null; 105 | String typeHandlerAlias = null; 106 | for (Map.Entry entry : propertiesMap.entrySet()) { 107 | String name = entry.getKey(); 108 | String value = entry.getValue(); 109 | if ("javaType".equals(name)) { 110 | javaType = resolveClass(value); 111 | builder.javaType(javaType); 112 | } else if ("jdbcType".equals(name)) { 113 | builder.jdbcType(resolveJdbcType(value)); 114 | } else if ("mode".equals(name)) { 115 | builder.mode(resolveParameterMode(value)); 116 | } else if ("numericScale".equals(name)) { 117 | builder.numericScale(Integer.valueOf(value)); 118 | } else if ("resultMap".equals(name)) { 119 | builder.resultMapId(value); 120 | } else if ("typeHandler".equals(name)) { 121 | typeHandlerAlias = value; 122 | } else if ("jdbcTypeName".equals(name)) { 123 | builder.jdbcTypeName(value); 124 | } else if ("property".equals(name)) { 125 | // Do Nothing 126 | } else if ("expression".equals(name)) { 127 | throw new BuilderException("Expression based parameters are not supported yet"); 128 | } else { 129 | throw new BuilderException("An invalid property '" + name + "' was found in mapping @{" + content 130 | + "}. Valid properties are " + VALID_PROPERTIES); 131 | } 132 | } 133 | if (typeHandlerAlias != null) { 134 | builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias)); 135 | } 136 | return builder.build(); 137 | } 138 | 139 | private Map parseParameterMapping(String content) { 140 | try { 141 | return new ParameterExpression(content); 142 | } catch (BuilderException ex) { 143 | throw ex; 144 | } catch (Exception ex) { 145 | throw new BuilderException("Parsing error was found in mapping @{" + content 146 | + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex); 147 | } 148 | } 149 | 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/RepeatDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.IOException; 19 | import java.io.StringWriter; 20 | import java.io.Writer; 21 | import java.util.Iterator; 22 | 23 | import org.apache.velocity.context.ChainedInternalContextAdapter; 24 | import org.apache.velocity.context.InternalContextAdapter; 25 | import org.apache.velocity.exception.TemplateInitException; 26 | import org.apache.velocity.exception.VelocityException; 27 | import org.apache.velocity.runtime.RuntimeServices; 28 | import org.apache.velocity.runtime.directive.Directive; 29 | import org.apache.velocity.runtime.directive.Scope; 30 | import org.apache.velocity.runtime.directive.StopCommand; 31 | import org.apache.velocity.runtime.parser.node.ASTReference; 32 | import org.apache.velocity.runtime.parser.node.ASTStringLiteral; 33 | import org.apache.velocity.runtime.parser.node.Node; 34 | import org.apache.velocity.runtime.parser.node.StandardParserTreeConstants; 35 | import org.apache.velocity.util.introspection.Info; 36 | 37 | /** 38 | * #repeat($collection $item SEP OPEN CLOSE). 39 | */ 40 | public class RepeatDirective extends Directive { 41 | 42 | protected static final int MAX_IN_CLAUSE_SIZE = 1000; 43 | 44 | /** Immutable fields */ 45 | private String var; 46 | private String open = ""; 47 | private String close = ""; 48 | private String separator = ""; 49 | protected Info uberInfo; 50 | 51 | @Override 52 | public String getName() { 53 | return "repeat"; 54 | } 55 | 56 | @Override 57 | public void init(RuntimeServices rs, InternalContextAdapter context, Node node) { 58 | super.init(rs, context, node); 59 | final int n = node.jjtGetNumChildren() - 1; 60 | for (int i = 1; i < n; i++) { 61 | Node child = node.jjtGetChild(i); 62 | if (i == 1) { 63 | if (child.getType() == StandardParserTreeConstants.JJTREFERENCE) { 64 | this.var = ((ASTReference) child).getRootString(); 65 | } else { 66 | throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn()); 67 | } 68 | } else if (child.getType() == StandardParserTreeConstants.JJTSTRINGLITERAL) { 69 | String value = (String) ((ASTStringLiteral) child).value(context); 70 | switch (i) { 71 | case 2: 72 | this.separator = value; 73 | break; 74 | case 3: 75 | this.open = value; 76 | break; 77 | case 4: 78 | this.close = value; 79 | break; 80 | default: 81 | break; 82 | } 83 | } else { 84 | throw new TemplateInitException("Syntax error", getTemplateName(), getLine(), getColumn()); 85 | } 86 | } 87 | this.uberInfo = new Info(this.getTemplateName(), getLine(), getColumn()); 88 | } 89 | 90 | @Override 91 | public boolean render(InternalContextAdapter context, Writer writer, Node node) throws IOException { 92 | 93 | Object listObject = node.jjtGetChild(0).value(context); 94 | 95 | if (listObject == null) { 96 | return false; 97 | } 98 | 99 | Iterator i = null; 100 | 101 | try { 102 | i = this.rsvc.getUberspect().getIterator(listObject, this.uberInfo); 103 | } catch (RuntimeException e) { 104 | throw e; 105 | } catch (Exception ee) { 106 | String msg = "Error getting iterator for #repeat at " + this.uberInfo; 107 | this.rsvc.getLog().error(msg, ee); 108 | throw new VelocityException(msg, ee); 109 | } 110 | 111 | if (i == null) { 112 | throw new VelocityException("Invalid collection"); 113 | } 114 | 115 | int counter = 0; 116 | boolean maxNbrLoopsExceeded = false; 117 | Object o = context.get(this.var); 118 | 119 | ParameterMappingCollector collector = (ParameterMappingCollector) context 120 | .get(SQLScriptSource.MAPPING_COLLECTOR_KEY); 121 | String savedItemKey = collector.getItemKey(); 122 | collector.setItemKey(this.var); 123 | RepeatScope foreach = new RepeatScope(this, context.get(getName()), this.var); 124 | context.put(getName(), foreach); 125 | 126 | NullHolderContext nullHolderContext = null; 127 | StringWriter buffer = new StringWriter(); 128 | while (!maxNbrLoopsExceeded && i.hasNext()) { 129 | Object value = i.next(); 130 | put(context, this.var, value); 131 | foreach.index++; 132 | foreach.hasNext = i.hasNext(); 133 | 134 | try { 135 | if (value == null) { 136 | if (nullHolderContext == null) { 137 | nullHolderContext = new NullHolderContext(this.var, context); 138 | } 139 | node.jjtGetChild(node.jjtGetNumChildren() - 1).render(nullHolderContext, buffer); 140 | } else { 141 | node.jjtGetChild(node.jjtGetNumChildren() - 1).render(context, buffer); 142 | } 143 | } catch (StopCommand stop) { 144 | if (stop.isFor(this)) { 145 | break; 146 | } 147 | clean(context, o, collector, savedItemKey); 148 | // close does not perform any action and this is here 149 | // to avoid eclipse reporting possible leak. 150 | buffer.close(); 151 | throw stop; 152 | } 153 | 154 | counter++; 155 | maxNbrLoopsExceeded = counter >= MAX_IN_CLAUSE_SIZE; 156 | 157 | if (i.hasNext() && !maxNbrLoopsExceeded) { 158 | buffer.append(this.separator); 159 | } 160 | 161 | } 162 | String content = buffer.toString().trim(); 163 | if (!"".equals(content)) { 164 | writer.append(this.open); 165 | writer.append(content); 166 | writer.append(this.close); 167 | } 168 | clean(context, o, collector, savedItemKey); 169 | return true; 170 | 171 | } 172 | 173 | protected void clean(InternalContextAdapter context, Object o, ParameterMappingCollector collector, 174 | String savedItemKey) { 175 | if (o != null) { 176 | context.put(this.var, o); 177 | } else { 178 | context.remove(this.var); 179 | } 180 | collector.setItemKey(savedItemKey); 181 | postRender(context); 182 | } 183 | 184 | protected void put(InternalContextAdapter context, String key, Object value) { 185 | context.put(key, value); 186 | } 187 | 188 | @Override 189 | public int getType() { 190 | return BLOCK; 191 | } 192 | 193 | public static class RepeatScope extends Scope { 194 | 195 | protected int index = -1; 196 | protected boolean hasNext = false; 197 | protected final String var; 198 | 199 | public RepeatScope(Object newOwner, Object replaces, String newVar) { 200 | super(newOwner, replaces); 201 | this.var = newVar; 202 | } 203 | 204 | public int getIndex() { 205 | return this.index; 206 | } 207 | 208 | public int getCount() { 209 | return this.index + 1; 210 | } 211 | 212 | public boolean hasNext() { 213 | return getHasNext(); 214 | } 215 | 216 | public boolean getHasNext() { 217 | return this.hasNext; 218 | } 219 | 220 | public boolean isFirst() { 221 | return this.index < 1; 222 | } 223 | 224 | public boolean getFirst() { 225 | return isFirst(); 226 | } 227 | 228 | public boolean isLast() { 229 | return !this.hasNext; 230 | } 231 | 232 | public boolean getLast() { 233 | return isLast(); 234 | } 235 | 236 | public String getVar() { 237 | return this.var; 238 | } 239 | 240 | } 241 | 242 | protected static class NullHolderContext extends ChainedInternalContextAdapter { 243 | 244 | private String loopVariableKey = ""; 245 | private boolean active = true; 246 | 247 | protected NullHolderContext(String key, InternalContextAdapter context) { 248 | super(context); 249 | if (key != null) { 250 | this.loopVariableKey = key; 251 | } 252 | } 253 | 254 | @Override 255 | public Object get(String key) { 256 | return (this.active && this.loopVariableKey.equals(key)) ? null : super.get(key); 257 | } 258 | 259 | @Override 260 | public Object put(String key, Object value) { 261 | if (this.loopVariableKey.equals(key) && (value == null)) { 262 | this.active = true; 263 | } 264 | 265 | return super.put(key, value); 266 | } 267 | 268 | @Override 269 | public Object remove(String key) { 270 | if (this.loopVariableKey.equals(key)) { 271 | this.active = false; 272 | } 273 | return super.remove(key); 274 | } 275 | } 276 | 277 | @Override 278 | public boolean isScopeProvided() { 279 | return true; 280 | } 281 | 282 | } 283 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/SQLScriptSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | import org.apache.ibatis.mapping.BoundSql; 22 | import org.apache.ibatis.mapping.ParameterMapping; 23 | import org.apache.ibatis.mapping.SqlSource; 24 | import org.apache.ibatis.session.Configuration; 25 | 26 | public class SQLScriptSource implements SqlSource { 27 | 28 | protected static final String PARAMETER_OBJECT_KEY = "_parameter"; 29 | protected static final String DATABASE_ID_KEY = "_databaseId"; 30 | protected static final String MAPPING_COLLECTOR_KEY = "_pmc"; 31 | protected static final String VARIABLES_KEY = "_vars"; 32 | 33 | private static int templateIndex = 0; 34 | 35 | private final ParameterMapping[] parameterMappingSources; 36 | private final Object compiledScript; 37 | private final Configuration configuration; 38 | 39 | public SQLScriptSource(Configuration newConfiguration, String script, Class parameterTypeClass) { 40 | this.configuration = newConfiguration; 41 | ParameterMappingSourceParser mappingParser = new ParameterMappingSourceParser(newConfiguration, script, 42 | parameterTypeClass); 43 | this.parameterMappingSources = mappingParser.getParameterMappingSources(); 44 | this.compiledScript = VelocityFacade.compile(mappingParser.getSql(), "velocity-template-" + (++templateIndex)); 45 | } 46 | 47 | @Override 48 | public BoundSql getBoundSql(Object parameterObject) { 49 | 50 | final Map context = new HashMap<>(); 51 | final ParameterMappingCollector pmc = new ParameterMappingCollector(this.parameterMappingSources, context, 52 | this.configuration); 53 | 54 | context.put(DATABASE_ID_KEY, this.configuration.getDatabaseId()); 55 | context.put(PARAMETER_OBJECT_KEY, parameterObject); 56 | context.put(MAPPING_COLLECTOR_KEY, pmc); 57 | context.put(VARIABLES_KEY, this.configuration.getVariables()); 58 | 59 | final String sql = VelocityFacade.apply(this.compiledScript, context); 60 | BoundSql boundSql = new BoundSql(this.configuration, sql, pmc.getParameterMappings(), parameterObject); 61 | for (Map.Entry entry : context.entrySet()) { 62 | boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); 63 | } 64 | 65 | return boundSql; 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/SetDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.IOException; 19 | import java.io.StringWriter; 20 | 21 | import org.apache.velocity.context.InternalContextAdapter; 22 | import org.apache.velocity.runtime.parser.node.ASTBlock; 23 | import org.apache.velocity.runtime.parser.node.Node; 24 | 25 | public class SetDirective extends TrimDirective { 26 | 27 | @Override 28 | public String getName() { 29 | return "mset"; 30 | } 31 | 32 | @Override 33 | protected Params getParams(InternalContextAdapter context, Node node) throws IOException { 34 | final Params params = new Params(); 35 | params.setPrefix("SET"); 36 | params.setSuffixOverrides(","); 37 | if (node.jjtGetNumChildren() == 1) { 38 | final Node child = node.jjtGetChild(0); 39 | if (child instanceof ASTBlock) { 40 | StringWriter blockContent = new StringWriter(); 41 | child.render(context, blockContent); 42 | params.setBody(blockContent.toString().trim()); 43 | return params; 44 | } 45 | } 46 | return null; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/TrimDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.IOException; 19 | import java.io.StringWriter; 20 | import java.io.Writer; 21 | import java.util.Locale; 22 | 23 | import org.apache.velocity.context.InternalContextAdapter; 24 | import org.apache.velocity.runtime.directive.Directive; 25 | import org.apache.velocity.runtime.parser.node.ASTBlock; 26 | import org.apache.velocity.runtime.parser.node.Node; 27 | 28 | public class TrimDirective extends Directive { 29 | 30 | @Override 31 | public String getName() { 32 | return "trim"; 33 | } 34 | 35 | @Override 36 | public final int getType() { 37 | return BLOCK; 38 | } 39 | 40 | @Override 41 | public final boolean render(InternalContextAdapter ica, Writer writer, Node node) throws IOException { 42 | Params p = getParams(ica, node); 43 | if (p == null) { 44 | return false; 45 | } 46 | return render(p, writer); 47 | } 48 | 49 | public boolean render(final Params params, final Writer writer) throws IOException { 50 | int leftIndex = 0; 51 | int rightIndex = params.maxBody; 52 | if (rightIndex == 0) { 53 | return false; 54 | } 55 | if (!params.prefixOverrides.isEmpty()) { 56 | final String LEFT = params.body 57 | .substring(0, params.maxPrefixLength < params.maxBody ? params.maxPrefixLength : params.maxBody) 58 | .toUpperCase(Locale.ENGLISH); 59 | FastLinkedList.Node n = params.prefixOverrides.start(); 60 | while (n != null) { 61 | if (LEFT.startsWith(n.data)) { 62 | leftIndex = n.data.length(); 63 | break; 64 | } 65 | n = n.next; 66 | } 67 | } 68 | if (!params.suffixOverrides.isEmpty()) { 69 | final String RIGHT = params.body 70 | .substring(rightIndex > params.maxSuffixLength ? rightIndex - params.maxSuffixLength : 0) 71 | .toUpperCase(Locale.ENGLISH); 72 | FastLinkedList.Node n = params.suffixOverrides.start(); 73 | while (n != null) { 74 | if (RIGHT.endsWith(n.data)) { 75 | rightIndex = rightIndex - n.data.length(); 76 | break; 77 | } 78 | n = n.next; 79 | } 80 | } 81 | if (rightIndex > leftIndex) { 82 | String content = params.body.substring(leftIndex, rightIndex).trim(); 83 | if (!"".equals(content)) { 84 | writer.append(params.prefix).append(' '); 85 | writer.append(params.body, leftIndex, rightIndex).append(' '); 86 | writer.append(params.suffix); 87 | } 88 | } 89 | return true; 90 | } 91 | 92 | protected static final class Params { 93 | 94 | String prefix = ""; 95 | 96 | String suffix = ""; 97 | 98 | FastLinkedList prefixOverrides = new FastLinkedList<>(); 99 | 100 | FastLinkedList suffixOverrides = new FastLinkedList<>(); 101 | 102 | String body = ""; 103 | 104 | int maxPrefixLength = 0; 105 | 106 | int maxSuffixLength = 0; 107 | 108 | int maxBody = 0; 109 | 110 | public String getBody() { 111 | return this.body; 112 | } 113 | 114 | public void setBody(String value) { 115 | if (value == null) { 116 | this.body = ""; 117 | } else { 118 | this.body = value.trim(); 119 | } 120 | this.maxBody = this.body.length(); 121 | } 122 | 123 | public String getPrefix() { 124 | return this.prefix; 125 | } 126 | 127 | public void setPrefix(String value) { 128 | this.prefix = value; 129 | } 130 | 131 | public FastLinkedList getPrefixOverrides() { 132 | return this.prefixOverrides; 133 | } 134 | 135 | public void setPrefixOverrides(String list) { 136 | this.maxPrefixLength = fromStringList(list, '|', this.prefixOverrides); 137 | } 138 | 139 | public FastLinkedList getSuffixOverrides() { 140 | return this.suffixOverrides; 141 | } 142 | 143 | public void setSuffixOverrides(String list) { 144 | this.maxSuffixLength = fromStringList(list, '|', this.suffixOverrides); 145 | } 146 | 147 | public String getSuffix() { 148 | return this.suffix; 149 | } 150 | 151 | public void setSuffix(String value) { 152 | this.suffix = value; 153 | } 154 | 155 | } 156 | 157 | protected Params getParams(final InternalContextAdapter context, final Node node) throws IOException { 158 | final Params params = new Params(); 159 | final int nodes = node.jjtGetNumChildren(); 160 | for (int i = 0; i < nodes; i++) { 161 | Node child = node.jjtGetChild(i); 162 | if (child != null) { 163 | if (!(child instanceof ASTBlock)) { 164 | if (i == 0) { 165 | params.setPrefix(String.valueOf(child.value(context))); 166 | } else if (i == 1) { 167 | params.setPrefixOverrides(String.valueOf(child.value(context)).toUpperCase(Locale.ENGLISH)); 168 | } else if (i == 2) { 169 | params.setSuffix(String.valueOf(child.value(context))); 170 | } else if (i == 3) { 171 | params.setSuffixOverrides(String.valueOf(child.value(context)).toUpperCase(Locale.ENGLISH)); 172 | } else { 173 | break; 174 | } 175 | } else { 176 | StringWriter blockContent = new StringWriter(); 177 | child.render(context, blockContent); 178 | params.setBody(blockContent.toString().trim()); 179 | break; 180 | } 181 | } 182 | } 183 | return params; 184 | } 185 | 186 | static int fromStringList(final String list, final char sep, final FastLinkedList fll) { 187 | int max = 0; 188 | if (list != null) { 189 | final int n = list.length(); 190 | int i = 0; 191 | while (i < n) { 192 | int r = list.indexOf(sep, i); 193 | if (i < r) { 194 | fll.add(list.substring(i, r)); 195 | int len = r - i; 196 | if (len > max) { 197 | max = len; 198 | } 199 | i = r + 1; 200 | } else { 201 | break; 202 | } 203 | } 204 | if (i < n) { 205 | fll.add(list.substring(i)); 206 | int len = n - i; 207 | if (len > max) { 208 | max = len; 209 | } 210 | } 211 | } 212 | return max; 213 | } 214 | 215 | } 216 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/VelocityFacade.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.StringReader; 19 | import java.io.StringWriter; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import java.util.Properties; 23 | import java.util.stream.Collectors; 24 | 25 | import org.apache.ibatis.builder.BuilderException; 26 | import org.apache.ibatis.io.Resources; 27 | import org.apache.ibatis.scripting.ScriptingException; 28 | import org.apache.velocity.Template; 29 | import org.apache.velocity.VelocityContext; 30 | import org.apache.velocity.runtime.RuntimeConstants; 31 | import org.apache.velocity.runtime.RuntimeInstance; 32 | import org.apache.velocity.runtime.parser.node.SimpleNode; 33 | 34 | public class VelocityFacade { 35 | 36 | private static final RuntimeInstance engine = new RuntimeInstance(); 37 | private static final Map additionalCtxAttributes = new HashMap<>(); 38 | 39 | private VelocityFacade() { 40 | // Prevent instantiation 41 | } 42 | 43 | /** 44 | * Initialize a template engine. 45 | * 46 | * @param driverConfig 47 | * a language driver configuration 48 | * 49 | * @since 2.1.0 50 | */ 51 | public static void initialize(VelocityLanguageDriverConfig driverConfig) { 52 | Properties properties = new Properties(); 53 | driverConfig.getVelocitySettings().forEach(properties::setProperty); 54 | properties.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, driverConfig.generateCustomDirectivesString()); 55 | engine.init(properties); 56 | additionalCtxAttributes.putAll(driverConfig.getAdditionalContextAttributes().entrySet().stream() 57 | .collect(Collectors.toMap(Map.Entry::getKey, v -> { 58 | try { 59 | return Resources.classForName(v.getValue()).getConstructor().newInstance(); 60 | } catch (Exception e) { 61 | throw new ScriptingException("Cannot load additional context attribute class.", e); 62 | } 63 | }))); 64 | } 65 | 66 | /** 67 | * Destroy a template engine. 68 | * 69 | * @since 2.1.0 70 | */ 71 | public static void destroy() { 72 | engine.reset(); 73 | additionalCtxAttributes.clear(); 74 | } 75 | 76 | public static Object compile(String script, String name) { 77 | try { 78 | StringReader reader = new StringReader(script); 79 | Template template = new Template(); 80 | SimpleNode node = engine.parse(reader, template); 81 | template.setRuntimeServices(engine); 82 | template.setData(node); 83 | template.setName(name); 84 | template.initDocument(); 85 | return template; 86 | } catch (Exception ex) { 87 | throw new BuilderException("Error parsing velocity script '" + name + "'", ex); 88 | } 89 | } 90 | 91 | public static String apply(Object template, Map context) { 92 | final StringWriter out = new StringWriter(); 93 | context.putAll(additionalCtxAttributes); 94 | ((Template) template).merge(new VelocityContext(context), out); 95 | return out.toString(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/VelocityLanguageDriver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import org.apache.ibatis.executor.parameter.ParameterHandler; 19 | import org.apache.ibatis.mapping.BoundSql; 20 | import org.apache.ibatis.mapping.MappedStatement; 21 | import org.apache.ibatis.mapping.SqlSource; 22 | import org.apache.ibatis.parsing.XNode; 23 | import org.apache.ibatis.scripting.LanguageDriver; 24 | import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; 25 | import org.apache.ibatis.session.Configuration; 26 | 27 | /** 28 | * The {@link LanguageDriver} using Velocity. 29 | *

30 | * This class rename from {@code Driver}. 31 | *

32 | * 33 | * @since 2.1.0 34 | * 35 | * @author Kazuki Shimizu 36 | */ 37 | public class VelocityLanguageDriver implements LanguageDriver { 38 | 39 | /** 40 | * Default constructor. 41 | */ 42 | public VelocityLanguageDriver() { 43 | this(VelocityLanguageDriverConfig.newInstance()); 44 | } 45 | 46 | /** 47 | * Constructor. 48 | * 49 | * @param driverConfig 50 | * a language driver configuration 51 | */ 52 | public VelocityLanguageDriver(VelocityLanguageDriverConfig driverConfig) { 53 | VelocityFacade.initialize(driverConfig); 54 | } 55 | 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | @Override 60 | public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, 61 | BoundSql boundSql) { 62 | return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | @Override 69 | public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterTypeClass) { 70 | return new SQLScriptSource(configuration, script.getNode().getTextContent(), 71 | parameterTypeClass == null ? Object.class : parameterTypeClass); 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | public SqlSource createSqlSource(Configuration configuration, String script, Class parameterTypeClass) { 79 | return new SQLScriptSource(configuration, script, parameterTypeClass == null ? Object.class : parameterTypeClass); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.io.InputStreamReader; 22 | import java.nio.charset.Charset; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.Collections; 25 | import java.util.HashMap; 26 | import java.util.HashSet; 27 | import java.util.Map; 28 | import java.util.Objects; 29 | import java.util.Optional; 30 | import java.util.Properties; 31 | import java.util.Set; 32 | import java.util.StringJoiner; 33 | import java.util.function.Consumer; 34 | import java.util.function.Function; 35 | import java.util.stream.Stream; 36 | 37 | import org.apache.commons.text.WordUtils; 38 | import org.apache.ibatis.io.Resources; 39 | import org.apache.ibatis.logging.Log; 40 | import org.apache.ibatis.logging.LogFactory; 41 | import org.apache.ibatis.reflection.DefaultReflectorFactory; 42 | import org.apache.ibatis.reflection.MetaObject; 43 | import org.apache.ibatis.reflection.factory.DefaultObjectFactory; 44 | import org.apache.ibatis.reflection.property.PropertyTokenizer; 45 | import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; 46 | import org.apache.ibatis.scripting.ScriptingException; 47 | import org.apache.velocity.runtime.RuntimeConstants; 48 | import org.apache.velocity.runtime.RuntimeInstance; 49 | import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; 50 | 51 | /** 52 | * Configuration class for {@link Driver}. 53 | * 54 | * @author Kazuki Shimizu 55 | * 56 | * @since 2.1.0 57 | */ 58 | public class VelocityLanguageDriverConfig { 59 | 60 | private static final String PROPERTY_KEY_CONFIG_FILE = "mybatis-velocity.config.file"; 61 | private static final String PROPERTY_KEY_CONFIG_ENCODING = "mybatis-velocity.config.encoding"; 62 | private static final String DEFAULT_PROPERTIES_FILE = "mybatis-velocity.properties"; 63 | private static final String PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE = "additional.context.attributes"; 64 | private static final String[] BUILT_IN_DIRECTIVES = { TrimDirective.class.getName(), WhereDirective.class.getName(), 65 | SetDirective.class.getName(), InDirective.class.getName(), RepeatDirective.class.getName() }; 66 | 67 | private static final Map, Function> TYPE_CONVERTERS; 68 | static { 69 | Map, Function> converters = new HashMap<>(); 70 | converters.put(String.class, String::trim); 71 | converters.put(Charset.class, v -> Charset.forName(v.trim())); 72 | converters.put(String[].class, v -> Stream.of(v.split(",")).map(String::trim).toArray(String[]::new)); 73 | converters.put(Object.class, v -> v); 74 | TYPE_CONVERTERS = Collections.unmodifiableMap(converters); 75 | } 76 | 77 | private static final Log log = LogFactory.getLog(VelocityLanguageDriverConfig.class); 78 | 79 | /** 80 | * The Velocity settings. 81 | */ 82 | private final Map velocitySettings = new HashMap<>(); 83 | { 84 | velocitySettings.put(RuntimeConstants.RESOURCE_LOADERS, "class"); 85 | velocitySettings.put(RuntimeConstants.RESOURCE_LOADER + ".class.class", ClasspathResourceLoader.class.getName()); 86 | } 87 | 88 | /** 89 | * The base directory for reading template resources. 90 | */ 91 | private String[] userDirectives = {}; 92 | 93 | /** 94 | * The additional context attribute. 95 | */ 96 | private final Map additionalContextAttributes = new HashMap<>(); 97 | 98 | /** 99 | * Get Velocity settings. 100 | * 101 | * @return Velocity settings 102 | */ 103 | public Map getVelocitySettings() { 104 | return velocitySettings; 105 | } 106 | 107 | /** 108 | * Get user define directives. 109 | * 110 | * @return user define directives. 111 | * 112 | * @deprecated Recommend to use the 'velocity-settings.runtime.custom_directives' or 'runtime.custom_directives' 113 | * because this method defined for keeping backward compatibility (There is possibility that this method 114 | * removed at a future version) 115 | */ 116 | @Deprecated 117 | public String[] getUserdirective() { 118 | return userDirectives; 119 | } 120 | 121 | /** 122 | * Set user define directives. 123 | * 124 | * @param userDirectives 125 | * user define directives 126 | * 127 | * @deprecated Recommend to use the 'velocity-settings.runtime.custom_directives' or 'runtime.custom_directives' 128 | * because this method defined for keeping backward compatibility (There is possibility that this method 129 | * removed at a future version) 130 | */ 131 | @Deprecated 132 | public void setUserdirective(String... userDirectives) { 133 | log.warn( 134 | "The 'userdirective' has been deprecated since 2.1.0. Please use the 'velocity-settings.runtime.custom_directives' or 'runtime.custom_directives'."); 135 | this.userDirectives = userDirectives; 136 | } 137 | 138 | /** 139 | * Get additional context attributes. 140 | * 141 | * @return additional context attributes 142 | */ 143 | public Map getAdditionalContextAttributes() { 144 | return additionalContextAttributes; 145 | } 146 | 147 | /** 148 | * Generate a custom directives string. 149 | * 150 | * @return a custom directives string 151 | */ 152 | public String generateCustomDirectivesString() { 153 | StringJoiner customDirectivesJoiner = new StringJoiner(","); 154 | Optional.ofNullable(velocitySettings.get(RuntimeConstants.CUSTOM_DIRECTIVES)) 155 | .ifPresent(customDirectivesJoiner::add); 156 | Stream.of(userDirectives).forEach(customDirectivesJoiner::add); 157 | Stream.of(BUILT_IN_DIRECTIVES).forEach(customDirectivesJoiner::add); 158 | return customDirectivesJoiner.toString(); 159 | } 160 | 161 | /** 162 | * Create an instance from default properties file.
163 | * If you want to customize a default {@link RuntimeInstance}, you can configure some property using 164 | * mybatis-velocity.properties that encoded by UTF-8. Also, you can change the properties file that will read using 165 | * system property (-Dmybatis-velocity.config.file=... -Dmybatis-velocity.config.encoding=...).
166 | * Supported properties are as follows: 167 | * 168 | * 169 | * 170 | * 171 | * 172 | * 173 | * 174 | * 175 | * 176 | * 177 | * 178 | * 179 | * 181 | * 182 | * 183 | * 184 | * 185 | * 186 | * 187 | * 188 | * 190 | * 191 | * 192 | * 193 | * 194 | * 195 | * 196 | * 197 | * 198 | * 199 | * 200 | * 201 | * 202 | * 203 | * 204 | * 205 | * 206 | * 207 | * 209 | * 210 | * 211 | *
Supported properties
Property KeyDescriptionDefault
Directive configuration
userdirectiveThe user defined directives (Recommend to use the 'velocity-settings.runtime.custom_directives' property 180 | * because this property defined for keeping backward compatibility)None(empty)
Additional context attribute configuration
additional.context.attributesThe user defined additional context attribute values(Recommend to use the 189 | * 'additional-context-attributes.{name}' because this property defined for keeping backward compatibility)None(empty)
additional-context-attributes.{name}The user defined additional context attributes value(FQCN)-
Velocity settings configuration
velocity-settings.{name}The settings of Velocity's {@link RuntimeInstance#setProperty(String, Object)}-
{name}The settings of Velocity's {@link RuntimeInstance#setProperty(String, Object)} (Recommend to use the 208 | * 'velocity-settings.{name}' because this property defined for keeping backward compatibility)-
212 | * 213 | * @return a configuration instance 214 | */ 215 | public static VelocityLanguageDriverConfig newInstance() { 216 | return newInstance(loadDefaultProperties()); 217 | } 218 | 219 | /** 220 | * Create an instance from specified properties. 221 | * 222 | * @param customProperties 223 | * custom configuration properties 224 | * 225 | * @return a configuration instance 226 | * 227 | * @see #newInstance() 228 | */ 229 | public static VelocityLanguageDriverConfig newInstance(Properties customProperties) { 230 | VelocityLanguageDriverConfig config = new VelocityLanguageDriverConfig(); 231 | Properties properties = loadDefaultProperties(); 232 | Optional.ofNullable(customProperties).ifPresent(properties::putAll); 233 | override(config, properties); 234 | configureVelocitySettings(config, properties); 235 | return config; 236 | } 237 | 238 | /** 239 | * Create an instance using specified customizer and override using a default properties file. 240 | * 241 | * @param customizer 242 | * baseline customizer 243 | * 244 | * @return a configuration instance 245 | * 246 | * @see #newInstance() 247 | */ 248 | public static VelocityLanguageDriverConfig newInstance(Consumer customizer) { 249 | VelocityLanguageDriverConfig config = new VelocityLanguageDriverConfig(); 250 | Properties properties = loadDefaultProperties(); 251 | customizer.accept(config); 252 | override(config, properties); 253 | configureVelocitySettings(config, properties); 254 | return config; 255 | } 256 | 257 | private static void override(VelocityLanguageDriverConfig config, Properties properties) { 258 | enableLegacyAdditionalContextAttributes(properties); 259 | MetaObject metaObject = MetaObject.forObject(config, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), 260 | new DefaultReflectorFactory()); 261 | Set consumedKeys = new HashSet<>(); 262 | properties.forEach((key, value) -> { 263 | String propertyPath = WordUtils 264 | .uncapitalize(WordUtils.capitalize(Objects.toString(key), '-').replaceAll("-", "")); 265 | if (metaObject.hasSetter(propertyPath)) { 266 | PropertyTokenizer pt = new PropertyTokenizer(propertyPath); 267 | if (Map.class.isAssignableFrom(metaObject.getGetterType(pt.getName()))) { 268 | @SuppressWarnings("unchecked") 269 | Map map = (Map) metaObject.getValue(pt.getName()); 270 | map.put(pt.getChildren(), value); 271 | } else { 272 | Optional.ofNullable(value).ifPresent(v -> { 273 | Object convertedValue = TYPE_CONVERTERS.get(metaObject.getSetterType(propertyPath)).apply(value.toString()); 274 | metaObject.setValue(propertyPath, convertedValue); 275 | }); 276 | } 277 | consumedKeys.add(key); 278 | } 279 | }); 280 | consumedKeys.forEach(properties::remove); 281 | } 282 | 283 | private static void enableLegacyAdditionalContextAttributes(Properties properties) { 284 | String additionalContextAttributes = properties.getProperty(PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE); 285 | if (Objects.nonNull(additionalContextAttributes)) { 286 | log.warn(String.format( 287 | "The '%s' has been deprecated since 2.1.0. Please use the 'additionalContextAttributes.{name}={value}'.", 288 | PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE)); 289 | Stream.of(additionalContextAttributes.split(",")).forEach(pair -> { 290 | String[] keyValue = pair.split(":"); 291 | if (keyValue.length != 2) { 292 | throw new ScriptingException("Invalid additional context property '" + pair + "' on '" 293 | + PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE + "'. Must be specify by 'key:value' format."); 294 | } 295 | properties.setProperty("additional-context-attributes." + keyValue[0].trim(), keyValue[1].trim()); 296 | }); 297 | properties.remove(PROPERTY_KEY_ADDITIONAL_CONTEXT_ATTRIBUTE); 298 | } 299 | } 300 | 301 | private static void configureVelocitySettings(VelocityLanguageDriverConfig config, Properties properties) { 302 | properties.forEach((name, value) -> config.getVelocitySettings().put((String) name, (String) value)); 303 | } 304 | 305 | private static Properties loadDefaultProperties() { 306 | return loadProperties(System.getProperty(PROPERTY_KEY_CONFIG_FILE, DEFAULT_PROPERTIES_FILE)); 307 | } 308 | 309 | private static Properties loadProperties(String resourcePath) { 310 | Properties properties = new Properties(); 311 | InputStream in; 312 | try { 313 | in = Resources.getResourceAsStream(resourcePath); 314 | } catch (IOException e) { 315 | in = null; 316 | } 317 | if (in != null) { 318 | Charset encoding = Optional.ofNullable(System.getProperty(PROPERTY_KEY_CONFIG_ENCODING)).map(Charset::forName) 319 | .orElse(StandardCharsets.UTF_8); 320 | try (InputStreamReader inReader = new InputStreamReader(in, encoding); 321 | BufferedReader bufReader = new BufferedReader(inReader)) { 322 | properties.load(bufReader); 323 | } catch (IOException e) { 324 | throw new IllegalStateException(e); 325 | } 326 | } 327 | return properties; 328 | } 329 | 330 | } 331 | -------------------------------------------------------------------------------- /src/main/java/org/mybatis/scripting/velocity/WhereDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.io.IOException; 19 | import java.io.StringWriter; 20 | 21 | import org.apache.velocity.context.InternalContextAdapter; 22 | import org.apache.velocity.runtime.parser.node.ASTBlock; 23 | import org.apache.velocity.runtime.parser.node.Node; 24 | 25 | public class WhereDirective extends TrimDirective { 26 | 27 | @Override 28 | public String getName() { 29 | return "where"; 30 | } 31 | 32 | @Override 33 | protected Params getParams(InternalContextAdapter context, Node node) throws IOException { 34 | final Params params = new Params(); 35 | params.setPrefix("WHERE"); 36 | params.setPrefixOverrides("AND |OR |AND\n|OR\n|AND\r|OR\r"); 37 | if (node.jjtGetNumChildren() == 1) { 38 | final Node child = node.jjtGetChild(0); 39 | if (child instanceof ASTBlock) { 40 | StringWriter blockContent = new StringWriter(); 41 | child.render(context, blockContent); 42 | params.setBody(blockContent.toString().trim()); 43 | return params; 44 | } 45 | } 46 | return null; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/site/xdoc/index.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 21 | 22 | 23 | MyBatis-Velocity 24 | The MyBatis Team 25 | 26 | 27 | 28 |
29 |

30 | mybatis-velocity is an extension that allows you to use the Apache Velocity scripting language 31 | to generate your dynamic SQL queries on the fly. 32 |

33 |

34 | If you are not familiar with apache velocity, you can learn it from its documentation site: 35 |

36 | 41 |
42 | 43 |
44 |
    45 |
  • Java : Java 8+
  • 46 |
  • MyBatis : 3.5+
  • 47 |
  • Velocity : 2.1+
  • 48 |
49 |
50 | 51 |
52 |

53 | If you are using maven, you can add this: 54 |

55 | 57 | org.mybatis.scripting 58 | mybatis-velocity 59 | 2.1.0 60 | 61 | ]]> 62 | 63 |

If you are using gradle, you can use this snippet:

64 | 65 | 69 | 70 |
71 | 72 |
73 | 74 |

75 | By default, the mybatis-velocity.properties file create in your classpath. 76 |

77 |
    78 |
  • 79 | You can specify user defined custom directives to use on the Velocity template engine using the Velocity standard property(runtime.custom_directives). 80 | For details see the "User Defined Directives". 81 | 86 | 87 | The userdirective property supported for keeping backward compatibility with old versions(2.0 or under). 88 | 91 |
  • 92 |
  • 93 | You can specify user defined additional context attribute values that passed to template engine. 94 | For details see the "Additional Context Attributes". 95 | 101 | 102 | The additional.context.attributes property supported for keeping backward compatibility with old versions(2.0 or under). 103 | 107 |
  • 108 |
  • 109 | You can configure the Velocity configuration as follow: 110 | About available setting name, please refer to the reference documentation of Velocity. 111 | 116 | 117 | The {name} = {value} format(without prefixed 'velocitySettings') supported for keeping backward compatibility with old versions(2.0 or under). 118 | 122 |
  • 123 |
  • 124 | Since 2.1.0, you can use an any properties file or encoding as follow: 125 | 128 |
  • 129 |
130 | 131 |
132 | 133 |

134 | You may need to do next steps:

135 |
    136 |
  • Register the language driver alias in your mybatis configuration file: 137 | 139 | ... 140 | 141 | 142 | 143 | ... 144 | ]]> 145 |
  • 146 |
  • Set the velocity as your default scripting language: 147 | 149 | ... 150 | 151 | 152 | 153 | ... 154 | ]]> 155 |
  • 156 |
157 |
158 |
159 | 160 |
161 |

Just write your dynamic queries and use velocity:

162 |

Example: 163 | 165 | #set( $pattern = $_parameter.name + '%' ) 166 | SELECT * 167 | FROM person 168 | WHERE name LIKE @{pattern, jdbcType=VARCHAR} 169 | 170 | ]]> 171 |

172 |

173 | Note: 174 |

175 |
    176 |
  • #{...} syntax is replaced by @{...} syntax to avoid collisions with VTL
  • 177 |
178 |
179 |
180 | 181 |
#trim( prefix prefixOverrides suffix suffixOverrides ) body #end
182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 |
ArgDescriptionTypeDefaultExample
prefixText to prepend if the body is not emptyStringEmpty String"WHERE "
prefixOverridesText to be removed from the body if it is at the beginningString list delimited by |Empty String"AND |OR "
suffixText to appended if the body is not emptyStringEmpty String"] "
suffixOverridesText to be removed from the body if it is at the endString list delimited by |Empty String","
223 |
224 | 225 |
#where() body #end
226 |

Removes any AND or OR from the beginning, then if the result is not empty, prepends WHERE at the beginning

227 |
228 | 229 |
#mset() body #end
230 |

Removes any "," from the end, then if the result is not empty, prepends SET at the beginning

231 |
232 | 233 |
#repeat( collection var separator open close ) body #end
234 |

235 | Same as #foreach, but with special features: 236 |

237 |
    238 |
  • Support for iterative parameter bindings via @{...} syntax
  • 239 |
  • Automatic separator and open/close string management
  • 240 |
241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 |
ArgDescriptionTypeDefaultExample
collectionList or array to iterateList or ArrayEmpty List$_parameter.selectedIds
varVariable to be used in the loopReference$item
separatorString to be inserted between iterationsStringEmpty String","
openString to be prepended at the beginningStringEmpty String"("
closeString to be appended at the endStringEmpty String")"
289 |

Example:

290 | 299 |
300 | 301 |
#in( collection var field ) body #end
302 |

303 | Generates a SQL IN clause and breaks it into groups of 1000 items seperated by an OR clause to get around database limitations on the maximum number of items that can be handled. 304 | It also supports iterative parameter bindings via @{...} syntax. 305 |

306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 |
ArgDescriptionTypeDefaultExample
collectionList or array to iterateList or ArrayEmpty List$_parameter.selectedIds
varVariable to be used in the loopReference$item
fieldField name to be inserted before the IN clauseStringEmpty String"state_id"
340 |

Example:

341 | 350 |
351 |
352 |
353 |

354 | mybatis-velocity allows you to write your own velocity directive: 355 |

356 |
    357 |
  • 358 | Create a property file named mybatis-velocity.properties and put in on the classpath. 359 |
  • 360 |
  • 361 | Create your own velocity directive. 362 |
  • 363 |
  • 364 | Add the directive to the property file. 365 |
  • 366 |
  • 367 | Use the directive in the sql template. 368 |
    369 |
  • 370 |
371 |

Example:

372 | 373 | 381 | 382 | 386 | 387 | 389 | SELECT * 390 | FROM City 391 | #myDirective() 392 | ...... 393 | #end 394 | ]]> 395 | 396 |
397 | 398 |
399 |

400 | The mybatis-velocity allows you to pass any attribute(instantiate using specified FQCN) to the Velocity template engine. 401 |

402 |
    403 |
  • 404 | Create a property file named mybatis-velocity.properties and put in on the classpath. 405 |
  • 406 |
  • 407 | Create any thread safe class. 408 |
  • 409 |
  • 410 | Add the class to the property file. 411 |
  • 412 |
  • 413 | Use object that instantiated by mybatis-velocity in your sql template. 414 |
  • 415 |
416 |

Example:

417 | 418 | 429 | 430 | 434 | 435 | 437 | #set( $pattern = $likeEscape.escape($_parameter.name) + "%" ) 438 | SELECT * FROM User 439 | WHERE name LIKE @{pattern, javaType=string} 440 | ]]> 441 |
442 | 443 |
444 | 445 |

@{ property, attr1=val1, attr2=val2, ... }

446 |

Where attrs must be any of: javaType, jdbcType, mode, numericScale, resultMap, typeHandler, jdbcTypeName

447 |
448 |
449 | 450 | 451 |
452 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/InDirectiveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | 20 | import java.io.StringWriter; 21 | import java.util.Arrays; 22 | import java.util.Collections; 23 | import java.util.HashMap; 24 | import java.util.Properties; 25 | 26 | import org.apache.ibatis.mapping.ParameterMapping; 27 | import org.apache.ibatis.session.Configuration; 28 | import org.apache.velocity.VelocityContext; 29 | import org.apache.velocity.app.VelocityEngine; 30 | import org.apache.velocity.runtime.RuntimeConstants; 31 | import org.junit.jupiter.api.BeforeAll; 32 | import org.junit.jupiter.api.Test; 33 | 34 | class InDirectiveTest { 35 | 36 | private static VelocityContext ctxt; 37 | private static VelocityEngine velocity; 38 | 39 | @BeforeAll 40 | static void setUpClass() { 41 | Properties p = new Properties(); 42 | p.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, InDirective.class.getName()); 43 | velocity = new VelocityEngine(); 44 | velocity.setProperty("runtime.log", "target/velocity.log"); 45 | velocity.init(p); 46 | ctxt = new VelocityContext(); 47 | ctxt.put(SQLScriptSource.MAPPING_COLLECTOR_KEY, 48 | new ParameterMappingCollector(new ParameterMapping[] {}, new HashMap<>(), new Configuration())); 49 | StringWriter writer = new StringWriter(); 50 | velocity.evaluate(ctxt, writer, "WARM", "1+1"); 51 | } 52 | 53 | @Test 54 | void ensureInClauseHasEmpty() { 55 | StringWriter w = new StringWriter(); 56 | ctxt.put("list", Collections.emptyList()); 57 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 58 | String result = w.toString(); 59 | assertEquals("((id NOT IN ( NULL )))", result); 60 | } 61 | 62 | @Test 63 | void ensureInClauseHasOne() { 64 | StringWriter w = new StringWriter(); 65 | ctxt.put("list", Collections.singletonList("?")); 66 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 67 | String result = w.toString(); 68 | assertEquals(1, result.split("\\?").length - 1); 69 | assertEquals(1, result.split("IN").length - 1); 70 | } 71 | 72 | @Test 73 | void ensureInClauseHasTwo() { 74 | StringWriter w = new StringWriter(); 75 | ctxt.put("list", Arrays.asList("?", "?")); 76 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 77 | String result = w.toString(); 78 | assertEquals(2, result.split("\\?").length - 1); 79 | } 80 | 81 | @Test 82 | void ensureInClauseHasOneThousand() { 83 | StringWriter w = new StringWriter(); 84 | String[] arr = new String[1000]; 85 | Arrays.fill(arr, "?"); 86 | ctxt.put("list", Arrays.asList(arr)); 87 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 88 | String result = w.toString(); 89 | assertEquals(1000, result.split("\\?").length - 1); 90 | assertEquals(0, result.split("OR").length - 1); 91 | } 92 | 93 | @Test 94 | void ensureInClauseHasOneThousandAndOne() { 95 | StringWriter w = new StringWriter(); 96 | String[] arr = new String[1001]; 97 | Arrays.fill(arr, "?"); 98 | ctxt.put("list", Arrays.asList(arr)); 99 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 100 | String result = w.toString(); 101 | assertEquals(1001, result.split("\\?").length - 1); 102 | assertEquals(1, result.split("OR").length - 1); 103 | } 104 | 105 | @Test 106 | void ensureInClauseHasTwoThousand() { 107 | StringWriter w = new StringWriter(); 108 | String[] arr = new String[2000]; 109 | Arrays.fill(arr, "?"); 110 | ctxt.put("list", Arrays.asList(arr)); 111 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 112 | String result = w.toString(); 113 | assertEquals(2000, result.split("\\?").length - 1); 114 | assertEquals(1, result.split("OR").length - 1); 115 | } 116 | 117 | @Test 118 | void ensureInClauseHasTwoThousandAndOne() { 119 | StringWriter w = new StringWriter(); 120 | String[] arr = new String[2001]; 121 | Arrays.fill(arr, "?"); 122 | ctxt.put("list", Arrays.asList(arr)); 123 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 124 | String result = w.toString(); 125 | assertEquals(2001, result.split("\\?").length - 1); 126 | assertEquals(2, result.split("OR").length - 1); 127 | } 128 | 129 | @Test 130 | void ensureInClauseHasThreeThousandAndOne() { 131 | StringWriter w = new StringWriter(); 132 | String[] arr = new String[3001]; 133 | Arrays.fill(arr, "?"); 134 | ctxt.put("list", Arrays.asList(arr)); 135 | velocity.evaluate(ctxt, w, "TEST", "#in($list $id 'id')?#end"); 136 | String result = w.toString(); 137 | assertEquals(3001, result.split("\\?").length - 1); 138 | assertEquals(3, result.split("OR").length - 1); 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/TrimDirectiveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | 20 | import java.io.StringWriter; 21 | import java.util.Properties; 22 | 23 | import org.apache.velocity.VelocityContext; 24 | import org.apache.velocity.app.VelocityEngine; 25 | import org.apache.velocity.runtime.RuntimeConstants; 26 | import org.junit.jupiter.api.BeforeAll; 27 | import org.junit.jupiter.api.Test; 28 | 29 | class TrimDirectiveTest { 30 | 31 | private static VelocityContext c; 32 | private static VelocityEngine velocity; 33 | 34 | @BeforeAll 35 | static void setUpClass() { 36 | Properties p = new Properties(); 37 | p.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, TrimDirective.class.getName()); 38 | velocity = new VelocityEngine(); 39 | velocity.setProperty("runtime.log", "target/velocity.log"); 40 | velocity.init(p); 41 | c = new VelocityContext(); 42 | StringWriter w = new StringWriter(); 43 | velocity.evaluate(c, w, "WARM", "1+1"); 44 | } 45 | 46 | @Test 47 | void simpleTest1() { 48 | StringWriter w = new StringWriter(); 49 | velocity.evaluate(c, w, "TEST", "#trim()SIMPLE WITHOUT PREFIX#end"); 50 | String result = w.toString(); 51 | assertEquals(" SIMPLE WITHOUT PREFIX ", result); 52 | } 53 | 54 | @Test 55 | void simpleTest2() { 56 | StringWriter w = new StringWriter(); 57 | velocity.evaluate(c, w, "TEST", "#trim() SIMPLE WITHOUT PREFIX #end"); 58 | String result = w.toString(); 59 | assertEquals(" SIMPLE WITHOUT PREFIX ", result); 60 | } 61 | 62 | @Test 63 | void simpleTest3() { 64 | StringWriter w = new StringWriter(); 65 | velocity.evaluate(c, w, "TEST", "#trim('WHERE') SIMPLE WITH PREFIX #end"); 66 | String result = w.toString(); 67 | assertEquals("WHERE SIMPLE WITH PREFIX ", result); 68 | } 69 | 70 | @Test 71 | void simpleTest4() { 72 | StringWriter w = new StringWriter(); 73 | velocity.evaluate(c, w, "TEST", "#trim('WHERE', 'simple') SIMPLE WITH PREFIX #end"); 74 | String result = w.toString(); 75 | assertEquals("WHERE WITH PREFIX ", result); 76 | } 77 | 78 | @Test 79 | void simpleTest5() { 80 | StringWriter w = new StringWriter(); 81 | velocity.evaluate(c, w, "TEST", "#trim('WHERE', '', '', 'prefix') SIMPLE WITH PREFIX #end"); 82 | String result = w.toString(); 83 | assertEquals("WHERE SIMPLE WITH ", result); 84 | } 85 | 86 | @Test 87 | void simpleTest6() { 88 | StringWriter w = new StringWriter(); 89 | velocity.evaluate(c, w, "TEST", "#trim('WHERE', 'simple', '', 'prefix') SIMPLE WITH PREFIX #end"); 90 | String result = w.toString(); 91 | assertEquals("WHERE WITH ", result); 92 | } 93 | 94 | @Test 95 | void simpleTest7() { 96 | StringWriter w = new StringWriter(); 97 | velocity.evaluate(c, w, "TEST", "#trim('WHERE', 'x', '', 'y') SIMPLE WITH PREFIX #end"); 98 | String result = w.toString(); 99 | assertEquals("WHERE SIMPLE WITH PREFIX ", result); 100 | } 101 | 102 | @Test 103 | void simpleTest8() { 104 | StringWriter w = new StringWriter(); 105 | velocity.evaluate(c, w, "TEST", "#trim('pre', 'x|y', 'pos', 'w|z') y---z #end"); 106 | String result = w.toString(); 107 | assertEquals("pre --- pos", result); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/VelocityLanguageDriverConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import java.nio.charset.StandardCharsets; 19 | import java.util.Properties; 20 | 21 | import org.apache.ibatis.scripting.ScriptingException; 22 | import org.junit.jupiter.api.AfterEach; 23 | import org.junit.jupiter.api.Assertions; 24 | import org.junit.jupiter.api.BeforeEach; 25 | import org.junit.jupiter.api.Test; 26 | 27 | class VelocityLanguageDriverConfigTest { 28 | 29 | private String currentConfigFile; 30 | private String currentConfigEncoding; 31 | 32 | @BeforeEach 33 | void saveCurrentConfig() { 34 | currentConfigFile = System.getProperty("mybatis-velocity.config"); 35 | currentConfigEncoding = System.getProperty("mybatis-velocity.config.encoding"); 36 | } 37 | 38 | @AfterEach 39 | void restoreConfig() { 40 | if (currentConfigFile == null) { 41 | System.clearProperty("mybatis-velocity.config.file"); 42 | } else { 43 | System.setProperty("mybatis-velocity.config.file", currentConfigFile); 44 | } 45 | if (currentConfigEncoding == null) { 46 | System.clearProperty("mybatis-velocity.config.encoding"); 47 | } else { 48 | System.setProperty("mybatis-velocity.config.encoding", currentConfigEncoding); 49 | } 50 | } 51 | 52 | @Test 53 | void newInstanceWithEmptyPropertiesFile() { 54 | System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-empty.properties"); 55 | VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); 56 | @SuppressWarnings("deprecation") 57 | String[] userDirectives = config.getUserdirective(); 58 | Assertions.assertEquals(0, userDirectives.length); 59 | Assertions.assertEquals(0, config.getAdditionalContextAttributes().size()); 60 | Assertions.assertEquals(2, config.getVelocitySettings().size()); 61 | Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); 62 | Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", 63 | config.getVelocitySettings().get("resource.loader.class.class")); 64 | Assertions.assertEquals( 65 | "org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", 66 | config.generateCustomDirectivesString()); 67 | } 68 | 69 | @Test 70 | void newInstanceWithPropertiesFileNotFound() { 71 | System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-notfound.properties"); 72 | VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); 73 | @SuppressWarnings("deprecation") 74 | String[] userDirectives = config.getUserdirective(); 75 | Assertions.assertEquals(0, userDirectives.length); 76 | Assertions.assertEquals(0, config.getAdditionalContextAttributes().size()); 77 | Assertions.assertEquals(2, config.getVelocitySettings().size()); 78 | Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); 79 | Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", 80 | config.getVelocitySettings().get("resource.loader.class.class")); 81 | Assertions.assertEquals( 82 | "org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", 83 | config.generateCustomDirectivesString()); 84 | } 85 | 86 | @Test 87 | void newInstanceWithCustomPropertiesFile() { 88 | System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-custom.properties"); 89 | VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); 90 | @SuppressWarnings("deprecation") 91 | String[] userDirectives = config.getUserdirective(); 92 | Assertions.assertEquals(0, userDirectives.length); 93 | Assertions.assertEquals(4, config.getAdditionalContextAttributes().size()); 94 | Assertions.assertEquals("org.mybatis.scripting.velocity.use.TrailingWildCardFormatter", 95 | config.getAdditionalContextAttributes().get("trailingWildCardFormatter")); 96 | Assertions.assertEquals("org.mybatis.scripting.velocity.use.EnumBinder", 97 | config.getAdditionalContextAttributes().get("enumBinder")); 98 | Assertions.assertEquals("attribute1Value", config.getAdditionalContextAttributes().get("attribute1")); 99 | Assertions.assertEquals("attribute2Value", config.getAdditionalContextAttributes().get("attribute2")); 100 | Assertions.assertEquals(7, config.getVelocitySettings().size()); 101 | Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); 102 | Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", 103 | config.getVelocitySettings().get("resource.loader.class.class")); 104 | Assertions.assertEquals("Windows-31J", config.getVelocitySettings().get("resource.default_encoding")); 105 | Assertions.assertEquals("100", config.getVelocitySettings().get("resource.manager.cache.default_size")); 106 | Assertions.assertEquals("20", config.getVelocitySettings().get("directive.foreach.max_loops")); 107 | Assertions.assertEquals("org.apache.velocity", config.getVelocitySettings().get("runtime.log.name")); 108 | Assertions.assertEquals( 109 | "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.use.CustomUserDirective2,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", 110 | config.generateCustomDirectivesString()); 111 | } 112 | 113 | @Test 114 | void newInstanceWithCustomProperties() { 115 | Properties properties = new Properties(); 116 | properties.setProperty("additional-context-attributes.trailingWildCardFormatter", 117 | "org.mybatis.scripting.velocity.use.TrailingWildCardFormatter"); 118 | properties.setProperty("additional-context-attributes.enumBinder", "org.mybatis.scripting.velocity.use.EnumBinder"); 119 | properties.setProperty("velocity-settings.resource.default_encoding", StandardCharsets.ISO_8859_1.name()); 120 | properties.setProperty("velocity-settings.resource.manager.cache.default_size", "200"); 121 | properties.setProperty("additional.context.attributes", 122 | "attribute1 : attribute1Value , attribute2 : attribute2Value"); 123 | properties.setProperty("directive.foreach.max_loops", "30"); 124 | properties.setProperty("runtime.log.name", "org.apache.velocity"); 125 | VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(properties); 126 | @SuppressWarnings("deprecation") 127 | String[] userDirectives = config.getUserdirective(); 128 | Assertions.assertEquals(0, userDirectives.length); 129 | Assertions.assertEquals(4, config.getAdditionalContextAttributes().size()); 130 | Assertions.assertEquals("org.mybatis.scripting.velocity.use.TrailingWildCardFormatter", 131 | config.getAdditionalContextAttributes().get("trailingWildCardFormatter")); 132 | Assertions.assertEquals("org.mybatis.scripting.velocity.use.EnumBinder", 133 | config.getAdditionalContextAttributes().get("enumBinder")); 134 | Assertions.assertEquals("attribute1Value", config.getAdditionalContextAttributes().get("attribute1")); 135 | Assertions.assertEquals("attribute2Value", config.getAdditionalContextAttributes().get("attribute2")); 136 | Assertions.assertEquals(7, config.getVelocitySettings().size()); 137 | Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); 138 | Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", 139 | config.getVelocitySettings().get("resource.loader.class.class")); 140 | Assertions.assertEquals(StandardCharsets.ISO_8859_1.name(), 141 | config.getVelocitySettings().get("resource.default_encoding")); 142 | Assertions.assertEquals("200", config.getVelocitySettings().get("resource.manager.cache.default_size")); 143 | Assertions.assertEquals("30", config.getVelocitySettings().get("directive.foreach.max_loops")); 144 | Assertions.assertEquals("org.apache.velocity", config.getVelocitySettings().get("runtime.log.name")); 145 | Assertions.assertEquals( 146 | "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", 147 | config.generateCustomDirectivesString()); 148 | } 149 | 150 | @Test 151 | void newInstanceWithLegacyPropertiesFile() { 152 | System.setProperty("mybatis-velocity.config.file", "mybatis-velocity-legacy.properties"); 153 | VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig.newInstance(); 154 | @SuppressWarnings("deprecation") 155 | String[] userDirectives = config.getUserdirective(); 156 | Assertions.assertEquals(1, userDirectives.length); 157 | Assertions.assertEquals(2, config.getAdditionalContextAttributes().size()); 158 | Assertions.assertEquals("org.mybatis.scripting.velocity.use.TrailingWildCardFormatter", 159 | config.getAdditionalContextAttributes().get("trailingWildCardFormatter")); 160 | Assertions.assertEquals("org.mybatis.scripting.velocity.use.EnumBinder", 161 | config.getAdditionalContextAttributes().get("enumBinder")); 162 | Assertions.assertEquals(4, config.getVelocitySettings().size()); 163 | Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); 164 | Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", 165 | config.getVelocitySettings().get("resource.loader.class.class")); 166 | Assertions.assertEquals("20", config.getVelocitySettings().get("directive.foreach.max_loops")); 167 | Assertions.assertEquals("org.apache.velocity", config.getVelocitySettings().get("runtime.log.name")); 168 | Assertions.assertEquals( 169 | "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", 170 | config.generateCustomDirectivesString()); 171 | } 172 | 173 | @Test 174 | void newInstanceWithConsumer() { 175 | VelocityLanguageDriverConfig config = VelocityLanguageDriverConfig 176 | .newInstance(c -> c.getVelocitySettings().put("resource.default_encoding", "Windows-31J")); 177 | Assertions.assertEquals(4, config.getVelocitySettings().size()); 178 | Assertions.assertEquals("class", config.getVelocitySettings().get("resource.loaders")); 179 | Assertions.assertEquals("org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader", 180 | config.getVelocitySettings().get("resource.loader.class.class")); 181 | Assertions.assertEquals("Windows-31J", config.getVelocitySettings().get("resource.default_encoding")); 182 | Assertions.assertEquals( 183 | "org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.TrimDirective,org.mybatis.scripting.velocity.WhereDirective,org.mybatis.scripting.velocity.SetDirective,org.mybatis.scripting.velocity.InDirective,org.mybatis.scripting.velocity.RepeatDirective", 184 | config.generateCustomDirectivesString()); 185 | } 186 | 187 | @Test 188 | void invalidAdditionalContextAttributeValue() { 189 | { 190 | Properties properties = new Properties(); 191 | properties.setProperty("additional.context.attributes", ""); 192 | try { 193 | VelocityLanguageDriverConfig.newInstance(properties); 194 | Assertions.fail(); 195 | } catch (ScriptingException e) { 196 | Assertions.assertEquals( 197 | "Invalid additional context property '' on 'additional.context.attributes'. Must be specify by 'key:value' format.", 198 | e.getMessage()); 199 | } 200 | } 201 | { 202 | Properties properties = new Properties(); 203 | properties.setProperty("additional.context.attributes", "key"); 204 | try { 205 | VelocityLanguageDriverConfig.newInstance(properties); 206 | Assertions.fail(); 207 | } catch (ScriptingException e) { 208 | Assertions.assertEquals( 209 | "Invalid additional context property 'key' on 'additional.context.attributes'. Must be specify by 'key:value' format.", 210 | e.getMessage()); 211 | } 212 | } 213 | { 214 | Properties properties = new Properties(); 215 | properties.setProperty("additional.context.attributes", "key:value:note"); 216 | try { 217 | VelocityLanguageDriverConfig.newInstance(properties); 218 | Assertions.fail(); 219 | } catch (ScriptingException e) { 220 | Assertions.assertEquals( 221 | "Invalid additional context property 'key:value:note' on 'additional.context.attributes'. Must be specify by 'key:value' format.", 222 | e.getMessage()); 223 | } 224 | } 225 | } 226 | 227 | } 228 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/WhereDirectiveTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | 20 | import java.io.StringWriter; 21 | import java.util.Properties; 22 | 23 | import org.apache.velocity.VelocityContext; 24 | import org.apache.velocity.app.VelocityEngine; 25 | import org.apache.velocity.runtime.RuntimeConstants; 26 | import org.junit.jupiter.api.BeforeAll; 27 | import org.junit.jupiter.api.Test; 28 | 29 | class WhereDirectiveTest { 30 | 31 | private static VelocityContext c; 32 | private static VelocityEngine velocity; 33 | 34 | @BeforeAll 35 | static void setUpClass() { 36 | Properties p = new Properties(); 37 | p.setProperty(RuntimeConstants.CUSTOM_DIRECTIVES, WhereDirective.class.getName()); 38 | velocity = new VelocityEngine(); 39 | velocity.setProperty("runtime.log", "target/velocity.log"); 40 | velocity.init(p); 41 | c = new VelocityContext(); 42 | StringWriter w = new StringWriter(); 43 | velocity.evaluate(c, w, "WARM", "1+1"); 44 | } 45 | 46 | @Test 47 | void simpleTest1() { 48 | StringWriter w = new StringWriter(); 49 | velocity.evaluate(c, w, "TEST", "#where()XXXX#end"); 50 | String result = w.toString(); 51 | assertEquals("WHERE XXXX ", result); 52 | } 53 | 54 | @Test 55 | void simpleTest2() { 56 | StringWriter w = new StringWriter(); 57 | velocity.evaluate(c, w, "TEST", "#where() a and b #end"); 58 | String result = w.toString(); 59 | assertEquals("WHERE a and b ", result); 60 | } 61 | 62 | @Test 63 | void simpleTest3() { 64 | StringWriter w = new StringWriter(); 65 | velocity.evaluate(c, w, "TEST", "#where() and a and b#end"); 66 | String result = w.toString(); 67 | assertEquals("WHERE a and b ", result); 68 | } 69 | 70 | @Test 71 | void simpleTest4() { 72 | StringWriter w = new StringWriter(); 73 | velocity.evaluate(c, w, "TEST", "#where() or a and b#end"); 74 | String result = w.toString(); 75 | assertEquals("WHERE a and b ", result); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/use/CustomUserDirective.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity.use; 17 | 18 | import java.io.IOException; 19 | import java.io.Writer; 20 | 21 | import org.apache.velocity.context.InternalContextAdapter; 22 | import org.apache.velocity.exception.MethodInvocationException; 23 | import org.apache.velocity.exception.ParseErrorException; 24 | import org.apache.velocity.exception.ResourceNotFoundException; 25 | import org.apache.velocity.runtime.directive.Directive; 26 | import org.apache.velocity.runtime.parser.node.Node; 27 | 28 | public class CustomUserDirective extends Directive { 29 | 30 | @Override 31 | public String getName() { 32 | return "genSql"; 33 | } 34 | 35 | @Override 36 | public int getType() { 37 | return BLOCK; 38 | } 39 | 40 | @Override 41 | public boolean render(InternalContextAdapter context, Writer writer, Node node) 42 | throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException { 43 | writer.append("SELECT * FROM Names"); 44 | return true; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/use/EnumBinder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity.use; 17 | 18 | public class EnumBinder { 19 | 20 | public EnumWrapper bind(String className) throws ClassNotFoundException { 21 | return new EnumWrapper(Class.forName(className)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/use/EnumWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity.use; 17 | 18 | import java.util.HashMap; 19 | 20 | public class EnumWrapper extends HashMap { 21 | 22 | private static final long serialVersionUID = 1L; 23 | 24 | public EnumWrapper(Class e) { 25 | if (e.isEnum()) { 26 | Object[] consts = e.getEnumConstants(); 27 | for (int i = 0; i < consts.length; i++) { 28 | put(consts[i].toString(), i); 29 | } 30 | } else { 31 | throw new IllegalArgumentException("Supplied argument is not an enum class"); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/use/Name.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity.use; 17 | 18 | public class Name { 19 | 20 | private int id; 21 | private String firstName; 22 | private String lastName; 23 | 24 | public Name() { 25 | } 26 | 27 | public Name(int value) { 28 | this.id = value; 29 | } 30 | 31 | public String getFirstName() { 32 | return this.firstName; 33 | } 34 | 35 | public void setFirstName(String value) { 36 | this.firstName = value; 37 | } 38 | 39 | public String getLastName() { 40 | return this.lastName; 41 | } 42 | 43 | public void setLastName(String value) { 44 | this.lastName = value; 45 | } 46 | 47 | public int getId() { 48 | return this.id; 49 | } 50 | 51 | public void setId(int value) { 52 | this.id = value; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/use/Parameter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity.use; 17 | 18 | public class Parameter { 19 | 20 | private final boolean includeLastName; 21 | 22 | private final String name; 23 | 24 | public Parameter(boolean newIncludeLastName, String newName) { 25 | this.includeLastName = newIncludeLastName; 26 | this.name = newName; 27 | } 28 | 29 | public boolean isIncludeLastName() { 30 | return this.includeLastName; 31 | } 32 | 33 | public String getName() { 34 | return this.name; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/use/TrailingWildCardFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity.use; 17 | 18 | public class TrailingWildCardFormatter { 19 | 20 | public String formatLiteral(Object val) { 21 | if (val == null) { 22 | return "''"; 23 | } 24 | String param = val.toString().replaceAll("\'", "\''"); 25 | return "'" + param + "%'"; 26 | } 27 | 28 | public String format(Object val) { 29 | if (val == null) { 30 | return ""; 31 | } 32 | return val + "%"; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/mybatis/scripting/velocity/use/VelocityLanguageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2022 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package org.mybatis.scripting.velocity.use; 17 | 18 | import static org.junit.jupiter.api.Assertions.*; 19 | 20 | import java.io.Reader; 21 | import java.sql.Connection; 22 | import java.sql.DriverManager; 23 | import java.util.ArrayList; 24 | import java.util.HashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | import org.apache.ibatis.io.Resources; 29 | import org.apache.ibatis.jdbc.ScriptRunner; 30 | import org.apache.ibatis.session.SqlSession; 31 | import org.apache.ibatis.session.SqlSessionFactory; 32 | import org.apache.ibatis.session.SqlSessionFactoryBuilder; 33 | import org.junit.jupiter.api.AfterAll; 34 | import org.junit.jupiter.api.BeforeAll; 35 | import org.junit.jupiter.api.Test; 36 | import org.mybatis.scripting.velocity.VelocityFacade; 37 | 38 | /** 39 | * Just a test case. Not a real Velocity implementation. 40 | */ 41 | class VelocityLanguageTest { 42 | 43 | private static SqlSessionFactory sqlSessionFactory; 44 | 45 | @SuppressWarnings("unused") 46 | public enum IDS { 47 | ZERO, ONE, TWO, THREE, FOUR, FIVE 48 | } 49 | 50 | @BeforeAll 51 | static void setUp() throws Exception { 52 | Connection conn = null; 53 | 54 | try { 55 | Class.forName("org.hsqldb.jdbcDriver"); 56 | conn = DriverManager.getConnection("jdbc:hsqldb:mem:bname", "sa", ""); 57 | 58 | Reader reader = Resources.getResourceAsReader("org/mybatis/scripting/velocity/use/CreateDB.sql"); 59 | 60 | ScriptRunner runner = new ScriptRunner(conn); 61 | runner.setLogWriter(null); 62 | runner.setErrorLogWriter(null); 63 | runner.runScript(reader); 64 | conn.commit(); 65 | reader.close(); 66 | 67 | reader = Resources.getResourceAsReader("org/mybatis/scripting/velocity/use/MapperConfig.xml"); 68 | sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); 69 | reader.close(); 70 | } finally { 71 | if (conn != null) { 72 | conn.close(); 73 | } 74 | } 75 | } 76 | 77 | @AfterAll 78 | static void cleanup() { 79 | VelocityFacade.destroy(); 80 | } 81 | 82 | @Test 83 | void testDynamicSelectWithPropertyParams() { 84 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 85 | 86 | Parameter p = new Parameter(true, "Fli%"); 87 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNames", p); 88 | assertEquals(3, answer.size()); 89 | for (Name n : answer) { 90 | assertEquals("Flintstone", n.getLastName()); 91 | } 92 | 93 | p = new Parameter(false, "Fli%"); 94 | answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNames", p); 95 | assertEquals(3, answer.size()); 96 | for (Name n : answer) { 97 | assertNull(n.getLastName()); 98 | } 99 | 100 | p = new Parameter(false, "Rub%"); 101 | answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNames", p); 102 | assertEquals(2, answer.size()); 103 | for (Name n : answer) { 104 | assertNull(n.getLastName()); 105 | } 106 | 107 | } 108 | } 109 | 110 | @Test 111 | void testDynamicSelectWithExpressionParams() { 112 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 113 | 114 | Parameter p = new Parameter(true, "Fli"); 115 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithExpressions", p); 116 | assertEquals(3, answer.size()); 117 | for (Name n : answer) { 118 | assertEquals("Flintstone", n.getLastName()); 119 | } 120 | 121 | p = new Parameter(false, "Fli"); 122 | answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithExpressions", p); 123 | assertEquals(3, answer.size()); 124 | for (Name n : answer) { 125 | assertNull(n.getLastName()); 126 | } 127 | 128 | p = new Parameter(false, "Rub"); 129 | answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithExpressions", p); 130 | assertEquals(2, answer.size()); 131 | for (Name n : answer) { 132 | assertNull(n.getLastName()); 133 | } 134 | 135 | } 136 | } 137 | 138 | @Test 139 | void testSelectNamesWithFormattedParam() { 140 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 141 | 142 | Parameter p = new Parameter(true, "Fli"); 143 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithFormattedParam", p); 144 | assertEquals(3, answer.size()); 145 | for (Name n : answer) { 146 | assertEquals("Flintstone", n.getLastName()); 147 | } 148 | 149 | } 150 | } 151 | 152 | @Test 153 | void testEnumBinding() { 154 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 155 | 156 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectEnumBinding"); 157 | assertEquals(3, answer.size()); 158 | for (Name n : answer) { 159 | assertEquals("Flintstone", n.getLastName()); 160 | } 161 | 162 | } 163 | } 164 | 165 | @Test 166 | void testSelectNamesWithFormattedParamSafe() { 167 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 168 | 169 | Parameter p = new Parameter(true, "Fli"); 170 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithFormattedParamSafe", 171 | p); 172 | assertEquals(3, answer.size()); 173 | for (Name n : answer) { 174 | assertEquals("Flintstone", n.getLastName()); 175 | } 176 | 177 | } 178 | } 179 | 180 | @Test 181 | void testDynamicSelectWithIteration() { 182 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 183 | 184 | int[] ids = { 2, 4, 5 }; 185 | Map param = new HashMap<>(); 186 | param.put("ids", ids); 187 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithIteration", param); 188 | assertEquals(3, answer.size()); 189 | for (int i = 0; i < ids.length; i++) { 190 | assertEquals(ids[i], answer.get(i).getId()); 191 | } 192 | 193 | } 194 | } 195 | 196 | @Test 197 | void testEmptyWhere() { 198 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 199 | 200 | int[] ids = {}; 201 | Map param = new HashMap<>(); 202 | param.put("ids", ids); 203 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithIteration", param); 204 | assertEquals(5, answer.size()); 205 | 206 | } 207 | } 208 | 209 | @Test 210 | void testTrim() { 211 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 212 | 213 | int[] ids = {}; 214 | Map param = new HashMap<>(); 215 | param.put("ids", ids); 216 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectWithTrim", param); 217 | assertEquals(5, answer.size()); 218 | 219 | } 220 | } 221 | 222 | @Test 223 | void testDynamicSelectWithIterationOverMap() { 224 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 225 | 226 | Map ids = new HashMap<>(); 227 | ids.put(2, "Wilma"); 228 | ids.put(4, "Barney"); 229 | ids.put(5, "Betty"); 230 | Map> param = new HashMap<>(); 231 | param.put("ids", ids); 232 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithIterationOverMap", 233 | param); 234 | assertEquals(3, answer.size()); 235 | for (Name n : answer) { 236 | assertEquals(ids.get(n.getId()), n.getFirstName()); 237 | } 238 | 239 | } 240 | } 241 | 242 | @Test 243 | void testDynamicSelectWithIterationComplex() { 244 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 245 | 246 | Name[] names = { new Name(2), new Name(4), new Name(5) }; 247 | Map param = new HashMap<>(); 248 | param.put("names", names); 249 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithIterationComplex", 250 | param); 251 | assertEquals(3, answer.size()); 252 | for (int i = 0; i < names.length; i++) { 253 | assertEquals(names[i].getId(), answer.get(i).getId()); 254 | } 255 | 256 | } 257 | } 258 | 259 | @Test 260 | void testDynamicSelectWithIterationBoundary() { 261 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 262 | 263 | List names = new ArrayList<>(); 264 | for (int i = 0; i < 1001; i++) { 265 | names.add(new Name(i)); 266 | } 267 | 268 | Map> param = new HashMap<>(); 269 | param.put("names", names); 270 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithIterationComplex", 271 | param); 272 | assertEquals(5, answer.size()); 273 | } 274 | } 275 | 276 | @Test 277 | void testSelectKey() { 278 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 279 | 280 | Name fred = new Name(); 281 | fred.setFirstName("Fred"); 282 | fred.setLastName("Flinstone"); 283 | sqlSession.insert("org.mybatis.scripting.velocity.use.insertName", fred); 284 | assertTrue(fred.getId() != 0); 285 | 286 | } 287 | } 288 | 289 | @Test 290 | void testSelectWithCustomUserDirective() { 291 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 292 | Map> param = new HashMap<>(); 293 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectWithCustomUserDirective", 294 | param); 295 | assertEquals(5, answer.size()); 296 | } 297 | } 298 | 299 | @Test 300 | void testDynamicSelectWithInDirectiveForOneThousandPlusOne() { 301 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 302 | List names = new ArrayList<>(); 303 | for (int i = 0; i < 1001; i++) { 304 | names.add(new Name(i + 1)); 305 | } 306 | 307 | Map> param = new HashMap<>(); 308 | param.put("names", names); 309 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithInDirective", param); 310 | assertEquals(5, answer.size()); 311 | } 312 | } 313 | 314 | @Test 315 | void testDynamicSelectWithInDirectiveForOneThousand() { 316 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 317 | List names = new ArrayList<>(); 318 | for (int i = 0; i < 1000; i++) { 319 | names.add(new Name(i + 1)); 320 | } 321 | 322 | Map> param = new HashMap<>(); 323 | param.put("names", names); 324 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithInDirective", param); 325 | assertEquals(5, answer.size()); 326 | } 327 | } 328 | 329 | @Test 330 | void testDynamicSelectWithInDirectiveForOneThousandMinusOne() { 331 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 332 | List names = new ArrayList<>(); 333 | for (int i = 0; i < 999; i++) { 334 | names.add(new Name(i + 1)); 335 | } 336 | 337 | Map> param = new HashMap<>(); 338 | param.put("names", names); 339 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithInDirective", param); 340 | assertEquals(5, answer.size()); 341 | } 342 | } 343 | 344 | @Test 345 | void testDynamicSelectWithInDirectiveForOneItem() { 346 | try (SqlSession sqlSession = sqlSessionFactory.openSession()) { 347 | List names = new ArrayList<>(); 348 | for (int i = 0; i < 1; i++) { 349 | names.add(new Name(i + 1)); 350 | } 351 | 352 | Map> param = new HashMap<>(); 353 | param.put("names", names); 354 | List answer = sqlSession.selectList("org.mybatis.scripting.velocity.use.selectNamesWithInDirective", param); 355 | assertEquals(1, answer.size()); 356 | } 357 | } 358 | 359 | @Test 360 | void testAdditionalContextAttributes() { 361 | Object template = VelocityFacade.compile("SELECT * FROM users WHERE id = ${id}", "test"); 362 | Map context = new HashMap<>(); 363 | context.put("id", 123); 364 | String sql = VelocityFacade.apply(template, context); 365 | assertEquals(3, context.size()); 366 | assertEquals(TrailingWildCardFormatter.class, context.get("trailingWildCardFormatter").getClass()); 367 | assertEquals(EnumBinder.class, context.get("enumBinder").getClass()); 368 | assertEquals("SELECT * FROM users WHERE id = 123", sql); 369 | } 370 | 371 | } 372 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2012-2022 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | ### Global logging configuration 18 | log4j.rootLogger=ERROR, stdout 19 | 20 | ### Uncomment for MyBatis logging 21 | log4j.logger.org.mybatis.scripting.velocity=trace 22 | 23 | ### Console output... 24 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 25 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 26 | log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n 27 | -------------------------------------------------------------------------------- /src/test/resources/mybatis-velocity-custom.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2012-2022 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | additional-context-attributes.trailingWildCardFormatter=org.mybatis.scripting.velocity.use.TrailingWildCardFormatter 18 | additional-context-attributes.enumBinder=org.mybatis.scripting.velocity.use.EnumBinder 19 | velocity-settings.resource.default_encoding = Windows-31J 20 | velocity-settings.resource.manager.cache.default_size = 100 21 | 22 | # For legacy style configuration 23 | runtime.custom_directives=org.mybatis.scripting.velocity.use.CustomUserDirective,org.mybatis.scripting.velocity.use.CustomUserDirective2 24 | additional.context.attributes=\ 25 | attribute1 : attribute1Value,\ 26 | attribute2 : attribute2Value 27 | directive.foreach.max_loops = 20 28 | runtime.log.name = org.apache.velocity 29 | -------------------------------------------------------------------------------- /src/test/resources/mybatis-velocity-empty.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2012-2022 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # 18 | -------------------------------------------------------------------------------- /src/test/resources/mybatis-velocity-legacy.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2012-2022 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | additional.context.attributes=trailingWildCardFormatter:org.mybatis.scripting.velocity.use.TrailingWildCardFormatter,enumBinder:org.mybatis.scripting.velocity.use.EnumBinder 18 | userdirective=org.mybatis.scripting.velocity.use.CustomUserDirective 19 | 20 | directive.foreach.max_loops = 20 21 | runtime.log.name = org.apache.velocity 22 | -------------------------------------------------------------------------------- /src/test/resources/mybatis-velocity.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2012-2022 the original author or authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | additional-context-attributes.trailingWildCardFormatter=org.mybatis.scripting.velocity.use.TrailingWildCardFormatter 18 | additional-context-attributes.enumBinder=org.mybatis.scripting.velocity.use.EnumBinder 19 | velocity-settings.runtime.custom_directives=org.mybatis.scripting.velocity.use.CustomUserDirective 20 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/velocity/use/CreateDB.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright 2012-2022 the original author or authors. 3 | -- 4 | -- Licensed under the Apache License, Version 2.0 (the "License"); 5 | -- you may not use this file except in compliance with the License. 6 | -- You may obtain a copy of the License at 7 | -- 8 | -- https://www.apache.org/licenses/LICENSE-2.0 9 | -- 10 | -- Unless required by applicable law or agreed to in writing, software 11 | -- distributed under the License is distributed on an "AS IS" BASIS, 12 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | -- See the License for the specific language governing permissions and 14 | -- limitations under the License. 15 | -- 16 | 17 | create table names ( 18 | id int generated by default as identity, 19 | firstName varchar(20), 20 | lastName varchar(20) 21 | ); 22 | 23 | insert into names (id, firstName, lastName) values(1, 'Fred', 'Flintstone'); 24 | insert into names (id, firstName, lastName) values(2, 'Wilma', 'Flintstone'); 25 | insert into names (id, firstName, lastName) values(3, 'Pebbles', 'Flintstone'); 26 | insert into names (id, firstName, lastName) values(4, 'Barney', 'Rubble'); 27 | insert into names (id, firstName, lastName) values(5, 'Betty', 'Rubble'); 28 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/velocity/use/MapperConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/test/resources/org/mybatis/scripting/velocity/use/mapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 22 | 23 | 24 | 25 | firstName #if($_parameter.includeLastName), lastName#end 26 | 27 | 28 | 'SOME NOISE', 29 | 30 | 31 | 36 | 37 | 43 | 44 | 49 | 50 | 56 | 57 | 66 | 67 | 74 | 75 | 84 | 85 | 94 | 95 | 99 | 100 | 108 | 109 | 110 | 111 | CALL IDENTITY() 112 | 113 | INSERT INTO names(firstName, lastName) 114 | VALUES (@{firstName}, @{lastName}) 115 | 116 | 117 | 124 | 125 | 126 | --------------------------------------------------------------------------------